Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> to variables * (like Class.staticMethod). * * When building scope information, also declares relevant information * about types in the type registry. * * @author nicksantos@google.com (Nick Santos) */ final class TypedScopeCreator implements ScopeCreator { /** * A suffix for naming delegate proxies differently from their base. */ static final String DELEGATE_PROXY_SUFFIX = ObjectType.createDelegateSuffix("Proxy"); static final DiagnosticType MALFORMED_TYPEDEF = DiagnosticType.warning( "JSC_MALFORMED_TYPEDEF", "Typedef for {0} does not have any type information"); static final DiagnosticType ENUM_INITIALIZER = DiagnosticType.warning( "JSC_ENUM_INITIALIZER_NOT_ENUM", "enum initializer must be an object literal or an enum"); static final DiagnosticType CTOR_INITIALIZER = DiagnosticType.warning( "JSC_CTOR_INITIALIZER_NOT_CTOR", "Constructor {0} must be initialized at declaration"); static final DiagnosticType IFACE_INITIALIZER = DiagnosticType.warning( "JSC_IFACE_INITIALIZER_NOT_IFACE", "Interface {0} must be initialized at declaration"); static final DiagnosticType CONSTRUCTOR_EXPECTED = DiagnosticType.warning( "JSC_REFLECT_CONSTRUCTOR_EXPECTED", "Constructor expected as first argument"); static final DiagnosticType UNKNOWN_LENDS = DiagnosticType.warning( "JSC_UNKNOWN_LENDS", "Variable {0} not declared before @lends annotation."); static final DiagnosticType LENDS_ON_NON_OBJECT = DiagnosticType.warning( "JSC_LENDS_ON_NON_OBJECT", "May only lend properties to object types. {0} has type {1}."); private final AbstractCompiler compiler; private final ErrorReporter typeParsingErrorReporter; private final TypeValidator validator; private final CodingConvention codingConvention; private final JSTypeRegistry typeRegistry; private final List<ObjectType> delegateProxyPrototypes = Lists.newArrayList(); private final Map<String, String> delegateCallingConventions = Maps.newHashMap(); // Simple properties inferred about functions. private final Map<Node, AstFunctionContents> functionAnalysisResults = Maps.newHashMap(); // For convenience private final ObjectType unknownType; /** * Defer attachment of types to nodes until all type names * have been resolved. Then, we can resolve the type and attach it. */ private class DeferredSetType { final Node node; final JSType type; DeferredSetType(Node node, JSType type) { Preconditions.checkNotNull(node); Preconditions.checkNotNull(type); this.node = node; this.type = type; // Other parts of this pass may read off the node. // (like when we set the LHS of an assign with a typed RHS function.) node.setJSType(type); } void resolve(Scope scope) { node.setJSType(type.resolve(typeParsingErrorReporter, scope)); } } TypedScopeCreator(AbstractCompiler compiler) { this(compiler, compiler.getCodingConvention()); } Typed

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> SCRIPT nodes // and a global typed scope should have been generated already. Preconditions.checkState(scriptRoot.isScript()); Preconditions.checkNotNull(globalScope); Preconditions.checkState(globalScope.isGlobal()); String scriptName = NodeUtil.getSourceName(scriptRoot); Preconditions.checkNotNull(scriptName); for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) { if (scriptName.equals(NodeUtil.getSourceName(node))) { functionAnalysisResults.remove(node); } } (new FirstOrderFunctionAnalyzer( compiler, functionAnalysisResults)).process(null, scriptRoot); // TODO(bashir): Variable declaration is not the only side effect of last // global scope generation but here we only wipe that part off! // Remove all variables that were previously declared in this scripts. // First find all vars to remove then remove them because of iterator! Iterator<Var> varIter = globalScope.getVars(); List<Var> varsToRemove = Lists.newArrayList(); while (varIter.hasNext()) { Var oldVar = varIter.next(); if (scriptName.equals(oldVar.getInputName())) { varsToRemove.add(oldVar); } } for (Var var : varsToRemove) { globalScope.undeclare(var); globalScope.getTypeOfThis().toObjectType().removeProperty(var.getName()); } // Now re-traverse the given script. GlobalScopeBuilder scopeBuilder = new GlobalScopeBuilder(globalScope); NodeTraversal.traverse(compiler, scriptRoot, scopeBuilder); } /** * Create the outermost scope. This scope contains native binding such as * {@code Object}, {@code Date}, etc. */ @VisibleForTesting Scope createInitialScope(Node root) { NodeTraversal.traverse( compiler, root, new DiscoverEnumsAndTypedefs(typeRegistry)); Scope s = Scope.createGlobalScope(root); declareNativeFunctionType(s, ARRAY_FUNCTION_TYPE); declareNativeFunctionType(s, BOOLEAN_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, DATE_FUNCTION_TYPE); declareNativeFunctionType(s, ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE); declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE); declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE); declareNativeValueType(s, "undefined", VOID_TYPE); // There is no longer a need to special case ActiveXObject // but this remains here until we can get the extern forks // cleaned up. declareNativeValueType(s, "ActiveXObject", FUNCTION_INSTANCE_TYPE); return s; } private

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> void declareNativeFunctionType(Scope scope, JSTypeNative tId) { FunctionType t = typeRegistry.getNativeFunctionType(tId); declareNativeType(scope, t.getInstanceType().getReferenceName(), t); declareNativeType( scope, t.getPrototype().getReferenceName(), t.getPrototype()); } private void declareNativeValueType(Scope scope, String name, JSTypeNative tId) { declareNativeType(scope, name, typeRegistry.getNativeType(tId)); } private static void declareNativeType(Scope scope, String name, JSType t) { scope.declare(name, null, t, null, false); } private static class DiscoverEnumsAndTypedefs extends AbstractShallowStatementCallback { private final JSTypeRegistry registry; DiscoverEnumsAndTypedefs(JSTypeRegistry registry) { this.registry = registry; } @Override public void visit(NodeTraversal t, Node node, Node parent) { switch (node.getType()) { case Token.VAR: for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { identifyNameNode( child, NodeUtil.getBestJSDocInfo(child)); } break; case Token.EXPR_RESULT: Node firstChild = node.getFirstChild(); if (firstChild.isAssign()) { identifyNameNode( firstChild.getFirstChild(), firstChild.getJSDocInfo()); } else { identifyNameNode( firstChild, firstChild.getJSDocInfo()); } break; } } private void identifyNameNode( Node nameNode, JSDocInfo info) { if (nameNode.isQualifiedName()) { if (info != null) { if (info.hasEnumParameterType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } else if (info.hasTypedefType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } } } } private JSType getNativeType(JSTypeNative nativeType) { return typeRegistry.getNativeType(nativeType); } private abstract class AbstractScopeBuilder implements NodeTraversal.Callback, Scope.TypeResolver { /** * The scope that we're building. */ final Scope scope; private final List<DeferredSetType> deferredSetTypes = Lists.newArrayList(); /** * Functions that we found in the global scope and not in externs. */ private final List<Node> nonExternFunctions = Lists.newArrayList(); /** * Object literals with a @lends annotation aren't analyzed until we * reach the root of the statement they're defined in. * * This ensures that if there are any @lends annotations on the object * literals, the type on the @lends annotation resolves correctly. * * For more information, see * http://code.google.com/p/closure-compiler/issues/detail?id=314 */ private List<Node> lentObjectLiterals = null; /** * Type-less stubs. * * If at the end of traversal, we still don't have types for these * stubs, then

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> we should declare UNKNOWN types. */ private final List<StubDeclaration> stubDeclarations = Lists.newArrayList(); /** * The current source file that we're in. */ private String sourceName = null; /** * The InputId of the current node. */ private InputId inputId; private AbstractScopeBuilder(Scope scope) { this.scope = scope; } void setDeferredType(Node node, JSType type) { deferredSetTypes.add(new DeferredSetType(node, type)); } @Override public void resolveTypes() { // Resolve types and attach them to nodes. for (DeferredSetType deferred : deferredSetTypes) { deferred.resolve(scope); } // Resolve types and attach them to scope slots. Iterator<Var> vars = scope.getVars(); while (vars.hasNext()) { vars.next().resolveType(typeParsingErrorReporter); } // Tell the type registry that any remaining types // are unknown. typeRegistry.resolveTypesInScope(scope); } @Override public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); if (n.isFunction() || n.isScript()) { Preconditions.checkNotNull(inputId); sourceName = NodeUtil.getSourceName(n); } // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. boolean descend = parent == null || !parent.isFunction() || n == parent.getFirstChild() || parent == scope.getRootNode(); if (descend) { // Handle hoisted functions on pre-order traversal, so that they // get hit before other things in the scope. if (NodeUtil.isStatementParent(n)) { for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (NodeUtil.isHoistedFunctionDeclaration(child)) { defineFunctionLiteral(child); } } } } return descend; } @Override public void visit(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); attachLiteralTypes(n); switch (n.getType()) { case Token.CALL: checkForClassDefiningCalls(t, n); checkForCallingConventionDefiningCalls(n, delegateCallingConventions); break; case Token.FUNCTION: if (t.getInput() == null || !t.getInput().isExtern()) { nonExternFunctions.add(n); } // Hoisted functions are handled during pre-traversal. if (!NodeUtil.isHoistedFunctionDeclaration(n)) { defineFunctionLiteral(n); } break; case Token.ASSIGN: // Handle initialization of properties. Node firstChild = n.getFirstChild(); if (firstChild.isGetProp() && firstChild.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), firstChild, n, firstChild.getNext()); } break; case Token.CATCH: defineCatch(n); break; case Token.VAR: defineVar(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>n); break; case Token.GETPROP: // Handle stubbed properties. if (parent.isExprResult() && n.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null); } break; } // Analyze any @lends object literals in this statement. if (n.getParent() != null && NodeUtil.isStatement(n) && lentObjectLiterals != null) { for (Node objLit : lentObjectLiterals) { defineObjectLiteral(objLit); } lentObjectLiterals.clear(); } } private void attachLiteralTypes(Node n) { switch (n.getType()) { case Token.NULL: n.setJSType(getNativeType(NULL_TYPE)); break; case Token.VOID: n.setJSType(getNativeType(VOID_TYPE)); break; case Token.STRING: n.setJSType(getNativeType(STRING_TYPE)); break; case Token.NUMBER: n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.TRUE: case Token.FALSE: n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.REGEXP: n.setJSType(getNativeType(REGEXP_TYPE)); break; case Token.OBJECTLIT: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.getLendsName() != null) { if (lentObjectLiterals == null) { lentObjectLiterals = Lists.newArrayList(); } lentObjectLiterals.add(n); } else { defineObjectLiteral(n); } break; // NOTE(nicksantos): If we ever support Array tuples, // we will need to put ARRAYLIT here as well. } } private void defineObjectLiteral(Node objectLit) { // Handle the @lends annotation. JSType type = null; JSDocInfo info = objectLit.getJSDocInfo(); if (info != null && info.getLendsName() != null) { String lendsName = info.getLendsName(); Var lendsVar = scope.getVar(lendsName); if (lendsVar == null) { compiler.report( JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName)); } else { type = lendsVar.getType(); if (type == null) { type = unknownType; } if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) { compiler.report( JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT, lendsName, type.toString())); type = null; } else { objectLit.setJSType(type); } } } info = NodeUtil.getBestJSDocInfo(objectLit); Node lValue = NodeUtil.getBestLValue(objectLit); String lValueName = NodeUtil.getBestLValueName

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(lValue); boolean createdEnumType = false; if (info != null && info.hasEnumParameterType()) { type = createEnumTypeFromNodes(objectLit, lValueName, info, lValue); createdEnumType = true; } if (type == null) { type = typeRegistry.createAnonymousObjectType(info); } setDeferredType(objectLit, type); // If this is an enum, the properties were already taken care of above. processObjectLitProperties( objectLit, ObjectType.cast(objectLit.getJSType()), !createdEnumType); } /** * Process an object literal and all the types on it. * @param objLit The OBJECTLIT node. * @param objLitType The type of the OBJECTLIT node. This might be a named * type, because of the lends annotation. * @param declareOnOwner If true, declare properties on the objLitType as * well. If false, the caller should take care of this. */ void processObjectLitProperties( Node objLit, ObjectType objLitType, boolean declareOnOwner) { for (Node keyNode = objLit.getFirstChild(); keyNode != null; keyNode = keyNode.getNext()) { Node value = keyNode.getFirstChild(); String memberName = NodeUtil.getObjectLitKeyName(keyNode); JSDocInfo info = keyNode.getJSDocInfo(); JSType valueType = getDeclaredType(info, keyNode, value); JSType keyType = objLitType.isEnumType() ? objLitType.toMaybeEnumType().getElementsType() : NodeUtil.getObjectLitKeyTypeFromValueType(keyNode, valueType); // Try to declare this property in the current scope if it // has an authoritative name. String qualifiedName = NodeUtil.getBestLValueName(keyNode); if (qualifiedName != null) { boolean inferred = keyType == null; defineSlot(keyNode, objLit, qualifiedName, keyType, inferred); } else if (keyType != null) { setDeferredType(keyNode, keyType); } if (keyType != null && objLitType != null && declareOnOwner) { // Declare this property on its object literal. objLitType.defineDeclaredProperty(memberName, keyType, keyNode); } } } /** * Returns the type specified in a JSDoc annotation near a GETPROP or NAME. * * Extracts type information from either the {@code @type} tag or from * the {@code @return} and {@code @param} tags. */ private JSType getDeclaredTypeInAnnotation(Node node, JSDocInfo info) { JSType jsType = null; if (info != null) { if (info.hasType()) { ImmutableList<TemplateType> ownerTypeKeys = ImmutableList.of(); Node ownerNode = NodeUtil.getBestLValueOwner(node); String ownerName = NodeUtil.getBestLValueName(ownerNode); ObjectType ownerType = null; if (ownerName != null) { Var ownerVar = scope.getVar(ownerName); if (ownerVar != null) { owner

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Type = getPrototypeOwnerType( ObjectType.cast(ownerVar.getType())); if (ownerType != null) { ownerTypeKeys = ownerType.getTemplateTypeMap().getTemplateKeys(); } } } if (!ownerTypeKeys.isEmpty()) { typeRegistry.setTemplateTypeNames(ownerTypeKeys); } jsType = info.getType().evaluate(scope, typeRegistry); if (!ownerTypeKeys.isEmpty()) { typeRegistry.clearTemplateTypeNames(); } } else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) { String fnName = node.getQualifiedName(); jsType = createFunctionTypeFromNodes( null, fnName, info, node); } } return jsType; } /** * Asserts that it's OK to define this node's name. * The node should have a source name and be of the specified type. */ void assertDefinitionNode(Node n, int type) { Preconditions.checkState(sourceName != null); Preconditions.checkState(n.getType() == type); } /** * Defines a catch parameter. */ void defineCatch(Node n) { assertDefinitionNode(n, Token.CATCH); Node catchName = n.getFirstChild(); defineSlot(catchName, n, getDeclaredType( catchName.getJSDocInfo(), catchName, null)); } /** * Defines a VAR initialization. */ void defineVar(Node n) { assertDefinitionNode(n, Token.VAR); JSDocInfo info = n.getJSDocInfo(); if (n.hasMoreThanOneChild()) { if (info != null) { // multiple children compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF)); } for (Node name : n.children()) { defineName(name, n, name.getJSDocInfo()); } } else { Node name = n.getFirstChild(); defineName(name, n, (info != null) ? info : name.getJSDocInfo()); } } /** * Defines a function literal. */ void defineFunctionLiteral(Node n) { assertDefinitionNode(n, Token.FUNCTION); // Determine the name and JSDocInfo and l-value for the function. // Any of these may be null. Node lValue = NodeUtil.getBestLValue(n); JSDocInfo info = NodeUtil.getBestJSDocInfo(n); String functionName = NodeUtil.getBestLValueName(lValue); FunctionType functionType = createFunctionTypeFromNodes(n, functionName, info, lValue); // Assigning the function type to the function node setDeferredType(n, functionType); // Declare this symbol in the current scope iff it's a function // declaration. Otherwise, the declaration will happen in other // code paths. if (NodeUtil.isFunctionDeclaration(n)) { defineSlot(n.getFirstChild(), n, functionType); } } /** * Defines a variable based on the {@link Token#NAME} node passed. * @param name The {@link Token#NAME} node. *

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> @param var The parent of the {@code name} node, which must be a * {@link Token#VAR} node. * @param info the {@link JSDocInfo} information relating to this * {@code name} node. */ private void defineName(Node name, Node var, JSDocInfo info) { Node value = name.getFirstChild(); // variable's type JSType type = getDeclaredType(info, name, value); if (type == null) { // The variable's type will be inferred. type = name.isFromExterns() ? unknownType : null; } defineSlot(name, var, type); } /** * If a variable is assigned a function literal in the global scope, * make that a declared type (even if there's no doc info). * There's only one exception to this rule: * if the return type is inferred, and we're in a local * scope, we should assume the whole function is inferred. */ private boolean shouldUseFunctionLiteralType( FunctionType type, JSDocInfo info, Node lValue) { if (info != null) { return true; } if (lValue != null && NodeUtil.isObjectLitKey(lValue)) { return false; } return scope.isGlobal() || !type.isReturnTypeInferred(); } /** * Creates a new function type, based on the given nodes. * * This handles two cases that are semantically very different, but * are not mutually exclusive: * - A function literal that needs a type attached to it. * - An assignment expression with function-type info in the JsDoc. * * All parameters are optional, and we will do the best we can to create * a function type. * * This function will always create a function type, so only call it if * you're sure that's what you want. * * @param rValue The function node. * @param name the function's name * @param info the {@link JSDocInfo} attached to the function definition * @param lvalueNode The node where this function is being * assigned. For example, {@code A.prototype.foo = ...} would be used to * determine that this function is a method of A.prototype. May be * null to indicate that this is not being assigned to a qualified name. */ private FunctionType createFunctionTypeFromNodes( @Nullable Node rValue, @Nullable String name, @Nullable JSDocInfo info, @Nullable Node lvalueNode) { FunctionType functionType = null; // Global ctor aliases should be registered with the type registry. if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) { Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() != null && var.getType().isFunctionType()) { FunctionType aliasedType = var.getType().toMaybeFunctionType(); if ((aliasedType.isConstructor() || aliasedType.isInterface()) && !aliasedType.isNativeObjectType()) { functionType = aliasedType; if (name

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> != null && scope.isGlobal()) { typeRegistry.declareType(name, functionType.getInstanceType()); } } } } if (functionType == null) { Node errorRoot = rValue == null ? lvalueNode : rValue; boolean isFnLiteral = rValue != null && rValue.isFunction(); Node fnRoot = isFnLiteral ? rValue : null; Node parametersNode = isFnLiteral ? rValue.getFirstChild().getNext() : null; if (info != null && info.hasType()) { JSType type = info.getType().evaluate(scope, typeRegistry); // Known to be not null since we have the FUNCTION token there. type = type.restrictByNotNullOrUndefined(); if (type.isFunctionType()) { functionType = type.toMaybeFunctionType(); functionType.setJSDocInfo(info); } } if (functionType == null) { // Find the type of any overridden function. Node ownerNode = NodeUtil.getBestLValueOwner(lvalueNode); String ownerName = NodeUtil.getBestLValueName(ownerNode); Var ownerVar = null; String propName = null; ObjectType ownerType = null; if (ownerName != null) { ownerVar = scope.getVar(ownerName); if (ownerVar != null) { ownerType = ObjectType.cast(ownerVar.getType()); } if (name != null) { propName = name.substring(ownerName.length() + 1); } } ObjectType prototypeOwner = getPrototypeOwnerType(ownerType); TemplateTypeMap prototypeOwnerTypeMap = null; if (prototypeOwner != null && prototypeOwner.getTypeOfThis() != null) { prototypeOwnerTypeMap = prototypeOwner.getTypeOfThis().getTemplateTypeMap(); } FunctionType overriddenType = null; if (ownerType != null && propName != null) { overriddenType = findOverriddenFunction( ownerType, propName, prototypeOwnerTypeMap); } FunctionTypeBuilder builder = new FunctionTypeBuilder(name, compiler, errorRoot, sourceName, scope) .setContents(getFunctionAnalysisResults(fnRoot)) .inferFromOverriddenFunction(overriddenType, parametersNode) .inferTemplateTypeName(info, prototypeOwner) .inferReturnType(info) .inferInheritance(info); // Infer the context type. boolean searchedForThisType = false; if (ownerType != null && ownerType.isFunctionPrototypeType() && ownerType.getOwnerFunction().hasInstanceType()) { builder.inferThisType( info, ownerType.getOwnerFunction().getInstanceType()); searchedForThisType = true; } else if (ownerNode != null && ownerNode.isThis()) { // If 'this' has a type, use that instead. // This is a hack, necessary because CollectProperties (below) // doesn't run with the scope that it's building, // so scope.getTypeOfThis() will be wrong. JSType injectedThisType = ownerNode.getJSType(); builder.inferThisType( info, injectedThisType == null ? scope.getTypeOfThis() : injectedThisType); searchedForThisType =

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> true; } if (!searchedForThisType) { builder.inferThisType(info); } functionType = builder .inferParameterTypes(parametersNode, info) .buildAndRegister(); } } // all done return functionType; } private ObjectType getPrototypeOwnerType(ObjectType ownerType) { if (ownerType != null && ownerType.isFunctionPrototypeType()) { return ownerType.getOwnerFunction(); } return null; } /** * Find the function that's being overridden on this type, if any. */ private FunctionType findOverriddenFunction( ObjectType ownerType, String propName, TemplateTypeMap typeMap) { FunctionType result = null; // First, check to see if the property is implemented // on a superclass. JSType propType = ownerType.getPropertyType(propName); if (propType != null && propType.isFunctionType()) { result = propType.toMaybeFunctionType(); } else { // If it's not, then check to see if it's implemented // on an implemented interface. for (ObjectType iface : ownerType.getCtorImplementedInterfaces()) { propType = iface.getPropertyType(propName); if (propType != null && propType.isFunctionType()) { result = propType.toMaybeFunctionType(); break; } } } if (result != null && typeMap != null && !typeMap.isEmpty()) { result = result.visit( new TemplateTypeMapReplacer(typeRegistry, typeMap)) .toMaybeFunctionType(); } return result; } /** * Creates a new enum type, based on the given nodes. * * This handles two cases that are semantically very different, but * are not mutually exclusive: * - An object literal that needs an enum type attached to it. * - An assignment expression with an enum tag in the JsDoc. * * This function will always create an enum type, so only call it if * you're sure that's what you want. * * @param rValue The node of the enum. * @param name The enum's name * @param info The {@link JSDocInfo} attached to the enum definition. * @param lValueNode The node where this function is being * assigned. */ private EnumType createEnumTypeFromNodes(Node rValue, String name, JSDocInfo info, Node lValueNode) { Preconditions.checkNotNull(info); Preconditions.checkState(info.hasEnumParameterType()); EnumType enumType = null; if (rValue != null && rValue.isQualifiedName()) { // Handle an aliased enum. Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() instanceof EnumType) { enumType = (EnumType) var.getType(); } } if (enumType == null) { JSType elementsType = info.getEnumParameterType().evaluate(scope, typeRegistry); enumType = typeRegistry.createEnumType(name, rValue, elementsType); if (rValue != null && rValue.isObjectLit()) { // collect enum elements Node key = rValue.getFirstChild

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(); while (key != null) { String keyName = NodeUtil.getStringValue(key); if (keyName == null) { // GET and SET don't have a String value; compiler.report( JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName)); } else if (!codingConvention.isValidEnumKey(keyName)) { compiler.report( JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName)); } else { enumType.defineElement(keyName, key); } key = key.getNext(); } } } if (name != null && scope.isGlobal()) { typeRegistry.declareType(name, enumType.getElementsType()); } return enumType; } /** * Defines a typed variable. The defining node will be annotated with the * variable's type or {@code null} if its type is inferred. * @param name the defining node. It must be a {@link Token#NAME}. * @param parent the {@code name}'s parent. * @param type the variable's type. It may be {@code null}, in which case * the variable's type will be inferred. */ private void defineSlot(Node name, Node parent, JSType type) { defineSlot(name, parent, type, type == null); } /** * Defines a typed variable. The defining node will be annotated with the * variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is * inferred. * * Slots may be any variable or any qualified name in the global scope. * * @param n the defining NAME or GETPROP node. * @param parent the {@code n}'s parent. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. */ void defineSlot(Node n, Node parent, JSType type, boolean inferred) { Preconditions.checkArgument(inferred || type != null); // Only allow declarations of NAMEs and qualified names. // Object literal keys will have to compute their names themselves. if (n.isName()) { Preconditions.checkArgument( parent.isFunction() || parent.isVar() || parent.isParamList() || parent.isCatch()); } else { Preconditions.checkArgument( n.isGetProp() && (parent.isAssign() || parent.isExprResult())); } defineSlot(n, parent, n.getQualifiedName(), type, inferred); } /** * Defines a symbol in the current scope. * * @param n the defining NAME or GETPROP or object literal key node. * @param parent the {@code n}'s parent. * @param variableName The name that this should be known by. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. * @param inferred Whether the type is inferred or declared. */ void defineSlot(Node n, Node parent, String variableName, JSType type, boolean inferred) { Preconditions.checkArgument(!variableName.isEmpty());

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> boolean isGlobalVar = n.isName() && scope.isGlobal(); boolean shouldDeclareOnGlobalThis = isGlobalVar && (parent.isVar() || parent.isFunction()); // If n is a property, then we should really declare it in the // scope where the root object appears. This helps out people // who declare "global" names in an anonymous namespace. Scope scopeToDeclareIn = scope; if (n.isGetProp() && !scope.isGlobal() && isQnameRootedInGlobalScope(n)) { Scope globalScope = scope.getGlobalScope(); // don't try to declare in the global scope if there's // already a symbol there with this name. if (!globalScope.isDeclared(variableName, false)) { scopeToDeclareIn = scope.getGlobalScope(); } } // The input may be null if we are working with a AST snippet. So read // the extern info from the node. Var newVar = null; // declared in closest scope? CompilerInput input = compiler.getInput(inputId); if (scopeToDeclareIn.isDeclared(variableName, false)) { Var oldVar = scopeToDeclareIn.getVar(variableName); newVar = validator.expectUndeclaredVariable( sourceName, input, n, parent, oldVar, variableName, type); } else { if (type != null) { setDeferredType(n, type); } newVar = scopeToDeclareIn.declare(variableName, n, type, input, inferred); if (type instanceof EnumType) { Node initialValue = newVar.getInitialValue(); boolean isValidValue = initialValue != null && (initialValue.isObjectLit() || initialValue.isQualifiedName()); if (!isValidValue) { compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER)); } } } // We need to do some additional work for constructors and interfaces. FunctionType fnType = JSType.toMaybeFunctionType(type); if (fnType != null && // We don't want to look at empty function types. !type.isEmptyType()) { // We want to make sure that when we declare a new instance type // (with @constructor) that there's actually a ctor for it. // This doesn't apply to structural constructors (like // function(new:Array). Checking the constructed type against // the variable name is a sufficient check for this. if ((fnType.isConstructor() || fnType.isInterface()) && variableName.equals(fnType.getReferenceName())) { finishConstructorDefinition(n, variableName, fnType, scopeToDeclareIn, input, newVar); } } if (shouldDeclareOnGlobalThis) { ObjectType globalThis = typeRegistry.getNativeObjectType(GLOBAL_THIS); if (inferred) { globalThis.defineInferredProperty(variableName, type == null ? getNativeType(JSTypeNative.NO_TYPE) : type, n); } else { globalThis.defineDeclaredProperty(variableName, type, n); } } if (isGlobalVar && "Window".equals(variableName) && type

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> != null && type.isFunctionType() && type.isConstructor()) { FunctionType globalThisCtor = typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor(); globalThisCtor.getInstanceType().clearCachedValues(); globalThisCtor.getPrototype().clearCachedValues(); globalThisCtor .setPrototypeBasedOn((type.toMaybeFunctionType()).getInstanceType()); } } private void finishConstructorDefinition( Node n, String variableName, FunctionType fnType, Scope scopeToDeclareIn, CompilerInput input, Var newVar) { // Declare var.prototype in the scope chain. FunctionType superClassCtor = fnType.getSuperClassConstructor(); Property prototypeSlot = fnType.getSlot("prototype"); // When we declare the function prototype implicitly, we // want to make sure that the function and its prototype // are declared at the same node. We also want to make sure // that the if a symbol has both a Var and a JSType, they have // the same node. // // This consistency is helpful to users of SymbolTable, // because everything gets declared at the same place. prototypeSlot.setNode(n); String prototypeName = variableName + ".prototype"; // There are some rare cases where the prototype will already // be declared. See TypedScopeCreatorTest#testBogusPrototypeInit. // Fortunately, other warnings will complain if this happens. Var prototypeVar = scopeToDeclareIn.getVar(prototypeName); if (prototypeVar != null && prototypeVar.scope == scopeToDeclareIn) { scopeToDeclareIn.undeclare(prototypeVar); } scopeToDeclareIn.declare(prototypeName, n, prototypeSlot.getType(), input, /* declared iff there's an explicit supertype */ superClassCtor == null || superClassCtor.getInstanceType().isEquivalentTo( getNativeType(OBJECT_TYPE))); // Make sure the variable is initialized to something if // it constructs itself. if (newVar.getInitialValue() == null && !n.isFromExterns()) { compiler.report( JSError.make(sourceName, n, fnType.isConstructor() ? CTOR_INITIALIZER : IFACE_INITIALIZER, variableName)); } } /** * Check if the given node is a property of a name in the global scope. */ private boolean isQnameRootedInGlobalScope(Node n) { Scope scope = getQnameRootScope(n); return scope != null && scope.isGlobal(); } /** * Return the scope for the name of the given node. */ private Scope getQnameRootScope(Node n) { Node root = NodeUtil.getRootOfQualifiedName(n); if (root.isName()) { Var var = scope.getVar(root.getString()); if (var != null) { return var.getScope(); } } return null; } /** * Look for a type declaration on a property assignment * (in an ASSIGN or an object literal key). * @param info The doc info for this property. * @param lValue The l-value node. * @param rValue The node that {@code n} is being initialized

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> to, * or {@code null} if this is a stub declaration. */ private JSType getDeclaredType(JSDocInfo info, Node lValue, @Nullable Node rValue) { if (info != null && info.hasType()) { return getDeclaredTypeInAnnotation(lValue, info); } else if (rValue != null && rValue.isFunction() && shouldUseFunctionLiteralType( JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) { return rValue.getJSType(); } else if (info != null) { if (info.hasEnumParameterType()) { if (rValue != null && rValue.isObjectLit()) { return rValue.getJSType(); } else { return createEnumTypeFromNodes( rValue, lValue.getQualifiedName(), info, lValue); } } else if (info.isConstructor() || info.isInterface()) { return createFunctionTypeFromNodes( rValue, lValue.getQualifiedName(), info, lValue); } } // Check if this is constant, and if it has a known type. if (isConstantSymbol(info, lValue)) { if (rValue != null) { JSDocInfo rValueInfo = rValue.getJSDocInfo(); if (rValueInfo != null && rValueInfo.hasType()) { // If rValue has a type-cast, we use the type in the type-cast. return rValueInfo.getType().evaluate(scope, typeRegistry); } else if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) { // If rValue's type was already computed during scope creation, // then we can safely use that. return rValue.getJSType(); } else if (rValue.isOr()) { // Check for a very specific JS idiom: // var x = x || TYPE; // This is used by Closure's base namespace for esoteric // reasons. Node firstClause = rValue.getFirstChild(); Node secondClause = firstClause.getNext(); boolean namesMatch = firstClause.isName() && lValue.isName() && firstClause.getString().equals(lValue.getString()); if (namesMatch && secondClause.getJSType() != null && !secondClause.getJSType().isUnknownType()) { return secondClause.getJSType(); } } } } return getDeclaredTypeInAnnotation(lValue, info); } private FunctionType getFunctionType(@Nullable Var v) { JSType t = v == null ? null : v.getType(); ObjectType o = t == null ? null : t.dereference(); return JSType.toMaybeFunctionType(o); } /** * Look for calls that set a delegate method's calling convention. */ private void checkForCallingConventionDefiningCalls( Node n, Map<String, String> delegateCallingConventions) { codingConvention.checkForCallingConventionDefiningCalls(n, delegateCallingConventions); } /** * Look for class-defining calls. * Because JS has no 'native' syntax for defining classes

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>, * this is often very coding-convention dependent and business-logic heavy. */ private void checkForClassDefiningCalls(NodeTraversal t, Node n) { SubclassRelationship relationship = codingConvention.getClassesDefinedByCall(n); if (relationship != null) { FunctionType superCtor = getFunctionType( scope.getVar(relationship.superclassName)); FunctionType subCtor = getFunctionType( scope.getVar(relationship.subclassName)); if (superCtor != null && superCtor.isConstructor() && subCtor != null && subCtor.isConstructor()) { ObjectType superClass = superCtor.getInstanceType(); ObjectType subClass = subCtor.getInstanceType(); // superCtor and subCtor might be structural constructors // (like {function(new:Object)}) so we need to resolve them back // to the original ctor objects. superCtor = superClass.getConstructor(); subCtor = subClass.getConstructor(); if (relationship.type == SubclassType.INHERITS && !superClass.isEmptyType() && !subClass.isEmptyType()) { validator.expectSuperType(t, n, superClass, subClass); } if (superCtor != null && subCtor != null) { codingConvention.applySubclassRelationship( superCtor, subCtor, relationship.type); } } } String singletonGetterClassName = codingConvention.getSingletonGetterClassName(n); if (singletonGetterClassName != null) { ObjectType objectType = ObjectType.cast( typeRegistry.getType(singletonGetterClassName)); if (objectType != null) { FunctionType functionType = objectType.getConstructor(); if (functionType != null) { FunctionType getterType = typeRegistry.createFunctionType(objectType); codingConvention.applySingletonGetter(functionType, getterType, objectType); } } } DelegateRelationship delegateRelationship = codingConvention.getDelegateRelationship(n); if (delegateRelationship != null) { applyDelegateRelationship(delegateRelationship); } ObjectLiteralCast objectLiteralCast = codingConvention.getObjectLiteralCast(n); if (objectLiteralCast != null) { if (objectLiteralCast.diagnosticType == null) { ObjectType type = ObjectType.cast( typeRegistry.getType(objectLiteralCast.typeName)); if (type != null && type.getConstructor() != null) { setDeferredType(objectLiteralCast.objectNode, type); objectLiteralCast.objectNode.putBooleanProp( Node.REFLECTED_OBJECT, true); } else { compiler.report(JSError.make(t.getSourceName(), n, CONSTRUCTOR_EXPECTED)); } } else { compiler.report(JSError.make(t.getSourceName(), n, objectLiteralCast.diagnosticType)); } } } /** * Apply special properties that only apply to delegates. */ private void applyDelegateRelationship( DelegateRelationship delegateRelationship) { ObjectType delegatorObject = ObjectType.cast( typeRegistry.getType(delegateRelationship.delegator)); ObjectType delegateBaseObject = ObjectType.cast( typeRegistry.getType(delegateRelationship.delegateBase)); ObjectType delegateSuperObject = ObjectType.cast( typeRegistry.getType(codingConvention.getDelegateSuperclassName()));

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(propName)) { Var qVar = scope.getVar(qName); if (qVar != null) { // If the programmer has declared that F inherits from Super, // and they assign F.prototype to an object literal, // then they are responsible for making sure that the object literal's // implicit prototype is set up appropriately. We just obey // the @extends tag. ObjectType qVarType = ObjectType.cast(qVar.getType()); if (qVarType != null && rhsValue != null && rhsValue.isObjectLit()) { typeRegistry.resetImplicitPrototype( rhsValue.getJSType(), qVarType.getImplicitPrototype()); } else if (!qVar.isTypeInferred()) { // If the programmer has declared that F inherits from Super, // and they assign F.prototype to some arbitrary expression, // there's not much we can do. We just ignore the expression, // and hope they've annotated their code in a way to tell us // what props are going to be on that prototype. return; } qVar.getScope().undeclare(qVar); } } if (valueType == null) { if (parent.isExprResult()) { stubDeclarations.add(new StubDeclaration( n, t.getInput() != null && t.getInput().isExtern(), ownerName)); } return; } boolean inferred = isQualifiedNameInferred( qName, n, info, rhsValue, valueType); if (!inferred) { ObjectType ownerType = getObjectSlot(ownerName); if (ownerType != null) { // Only declare this as an official property if it has not been // declared yet. boolean isExtern = t.getInput() != null && t.getInput().isExtern(); if ((!ownerType.hasOwnProperty(propName) || ownerType.isPropertyTypeInferred(propName)) && ((isExtern && !ownerType.isNativeObjectType()) || !ownerType.isInstanceType())) { // If the property is undeclared or inferred, declare it now. ownerType.defineDeclaredProperty(propName, valueType, n); } } // If the property is already declared, the error will be // caught when we try to declare it in the current scope. defineSlot(n, parent, valueType, inferred); } else if (rhsValue != null && rhsValue.isTrue()) { // We declare these for delegate proxy method properties. ObjectType ownerType = getObjectSlot(ownerName); FunctionType ownerFnType = JSType.toMaybeFunctionType(ownerType); if (ownerFnType != null) { JSType ownerTypeOfThis = ownerFnType.getTypeOfThis(); String delegateName = codingConvention.getDelegateSuperclassName(); JSType delegateType = delegateName == null ? null : typeRegistry.getType(delegateName); if (delegateType != null && ownerTypeOfThis.isSubtype(delegateType)) { defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true); } } } } /** * Determines whether a qualified name is inferred. * NOTE(nicksantos): Determining whether a property is declared or not * is really really ob

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>noxious. * * The problem is that there are two (equally valid) coding styles: * * (function() { * /* The authoritative definition of goog.bar. / * goog.bar = function() {}; * })(); * * function f() { * goog.bar(); * /* Reset goog.bar to a no-op. / * goog.bar = function() {}; * } } else if (!scope.isDeclared(qName, false) && n.isUnscopedQualifiedName()) { // Check if this is in a conditional block. // Functions assigned in conditional blocks are inferred. for (Node current = n.getParent(); !(current.isScript() || current.isFunction()); current = current.getParent()) { if (NodeUtil.isControlStructure(current)) { return true; } } // Check if this is assigned in an inner scope. // Functions assigned in inner scopes are inferred. AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents == null || !contents.getEscapedQualifiedNames().contains(qName)) { return false; } } } return inferred; } private boolean isConstantSymbol(JSDocInfo info, Node node) { if (info != null && info.isConstant()) { return true; } switch (node.getType()) { case Token.NAME: return NodeUtil.isConstantByConvention( compiler.getCodingConvention(), node, node.getParent()); case Token.GETPROP: return node.isQualifiedName() && NodeUtil.isConstantByConvention( compiler.getCodingConvention(), node.getLastChild(), node); } return false; } /** * Find the ObjectType associated with the given slot. * @param slotName The name of the slot to find the type in. * @return An object type, or null if this slot does not contain an object. */ private ObjectType getObjectSlot(String slotName) { Var ownerVar = scope.getVar(slotName); if (ownerVar != null) { JSType ownerVarType = ownerVar.getType(); return ObjectType.cast(ownerVarType == null ? null : ownerVarType.restrictByNotNullOrUndefined()); } return null; } /** * Resolve any stub declarations to unknown types if we could not * find types for them during traversal. */ void resolveStubDeclarations() { for (StubDeclaration stub : stubDeclarations) { Node n = stub.node; Node parent = n.getParent(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); String ownerName = stub.ownerName; boolean isExtern = stub.isExtern; if (scope.isDeclared(qName, false)) { continue; } // If we see a stub property, make sure to register this property // in the type registry. ObjectType ownerType = getObjectSlot(ownerName); defineSlot(n, parent, unknownType, true); if (ownerType != null && (isExtern || ownerType.isFunctionPrototypeType())) { // If this is a stub for

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> a prototype, just declare it // as an unknown type. These are seen often in externs. ownerType.defineInferredProperty( propName, unknownType, n); } else { typeRegistry.registerPropertyOnType( propName, ownerType == null ? unknownType : ownerType); } } } /** * Collects all declared properties in a function, and * resolves them relative to the global scope. */ private final class CollectProperties extends AbstractShallowStatementCallback { private final JSType thisType; CollectProperties(JSType thisType) { this.thisType = thisType; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isExprResult()) { Node child = n.getFirstChild(); switch (child.getType()) { case Token.ASSIGN: maybeCollectMember(child.getFirstChild(), child, child.getLastChild()); break; case Token.GETPROP: maybeCollectMember(child, child, null); break; } } } private void maybeCollectMember(Node member, Node nodeWithJsDocInfo, @Nullable Node value) { JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo(); // Do nothing if there is no JSDoc type info, or // if the node is not a member expression, or // if the member expression is not of the form: this.someProperty. if (info == null || !member.isGetProp() || !member.getFirstChild().isThis()) { return; } member.getFirstChild().setJSType(thisType); // TODO(johnlenz): We are evaluating these values in the wrong scope: // https://code.google.com/p/closure-compiler/issues/detail?id=926 JSType thisObjectType = thisType.toObjectType(); if (thisObjectType != null) { ImmutableList<TemplateType> keys = thisObjectType.getTemplateTypeMap().getTemplateKeys(); typeRegistry.setTemplateTypeNames(keys); } JSType jsType = getDeclaredType(info, member, value); if (thisObjectType != null) { typeRegistry.clearTemplateTypeNames(); } Node name = member.getLastChild(); if (jsType != null && (name.isName() || name.isString()) && thisType.toObjectType() != null) { thisType.toObjectType().defineDeclaredProperty( name.getString(), jsType, member); } } } // end CollectProperties } /** * A stub declaration without any type information. */ private static final class StubDeclaration { private final Node node; private final boolean isExtern; private final String ownerName; private StubDeclaration(Node node, boolean isExtern, String ownerName) { this.node = node; this.isExtern = isExtern; this.ownerName = ownerName; } } /** * A shallow traversal of the global scope to build up all classes, * functions, and methods. */ private final class GlobalScopeBuilder extends AbstractScopeBuilder { private GlobalScopeBuilder(Scope scope) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> super(scope); } /** * Visit a node in the global scope, and add anything it declares to the * global symbol table. * * @param t The current traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { super.visit(t, n, parent); switch (n.getType()) { case Token.VAR: // Handle typedefs. if (n.hasOneChild()) { checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo()); } break; } } @Override void maybeDeclareQualifiedName( NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { checkForTypedef(t, n, info); super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue); } /** * Handle typedefs. * @param t The current traversal. * @param candidate A qualified name node. * @param info JSDoc comments. */ private void checkForTypedef( NodeTraversal t, Node candidate, JSDocInfo info) { if (info == null || !info.hasTypedefType()) { return; } String typedef = candidate.getQualifiedName(); if (typedef == null) { return; } // TODO(nicksantos|user): This is a terrible, terrible hack // to bail out on recursive typedefs. We'll eventually need // to handle these properly. typeRegistry.declareType(typedef, unknownType); JSType realType = info.getTypedefType().evaluate(scope, typeRegistry); if (realType == null) { compiler.report( JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.overwriteDeclaredType(typedef, realType); if (candidate.isGetProp()) { defineSlot(candidate, candidate.getParent(), getNativeType(NO_TYPE), false); } } } // end GlobalScopeBuilder /** * A shallow traversal of a local scope to find all arguments and * local variables. */ private final class LocalScopeBuilder extends AbstractScopeBuilder { /** * @param scope The scope that we're building. */ private LocalScopeBuilder(Scope scope) { super(scope); } /** * Traverse the scope root and build it. */ void build() { NodeTraversal.traverse(compiler, scope.getRootNode(), this); AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents != null) { for (String varName : contents.getEscapedVarNames()) { Var v = scope.getVar(varName); Preconditions.checkState(v.getScope() == scope); v.markEscaped(); } for (Multiset.Entry<String> entry : contents.getAssignedNameCounts().entrySet()) { Var v = scope.getVar(entry.getElement()); Preconditions.checkState(v.getScope() == scope); if (entry.getCount() == 1) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> v.markAssignedExactlyOnce(); } } } } /** * Visit a node in a local scope, and add any local variables or catch * parameters into the local symbol table. * * @param t The node traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n == scope.getRootNode()) { return; } if (n.isParamList() && parent == scope.getRootNode()) { handleFunctionInputs(parent); return; } super.visit(t, n, parent); } /** Handle bleeding functions and function parameters. */ private void handleFunctionInputs(Node fnNode) { // Handle bleeding functions. Node fnNameNode = fnNode.getFirstChild(); String fnName = fnNameNode.getString(); if (!fnName.isEmpty()) { Scope.Var fnVar = scope.getVar(fnName); if (fnVar == null || // Make sure we're not touching a native function. Native // functions aren't bleeding, but may not have a declaration // node. (fnVar.getNameNode() != null && // Make sure that the function is actually bleeding by checking // if has already been declared. fnVar.getInitialValue() != fnNode)) { defineSlot(fnNameNode, fnNode, fnNode.getJSType(), false); } } declareArguments(fnNode); } /** * Declares all of a function's arguments. */ private void declareArguments(Node functionNode) { Node astParameters = functionNode.getFirstChild().getNext(); Node iifeArgumentNode = null; if (NodeUtil.isCallOrNewTarget(functionNode)) { iifeArgumentNode = functionNode.getNext(); } FunctionType functionType = JSType.toMaybeFunctionType(functionNode.getJSType()); if (functionType != null) { Node jsDocParameters = functionType.getParametersNode(); if (jsDocParameters != null) { Node jsDocParameter = jsDocParameters.getFirstChild(); for (Node astParameter : astParameters.children()) { JSType paramType = jsDocParameter == null ? unknownType : jsDocParameter.getJSType(); boolean inferred = paramType == null || paramType == unknownType; if (iifeArgumentNode != null && inferred) { String argumentName = iifeArgumentNode.getQualifiedName(); Var argumentVar = argumentName == null || scope.getParent() == null ? null : scope.getParent().getVar(argumentName); if (argumentVar != null && !argumentVar.isTypeInferred()) { paramType = argumentVar.getType(); } } if (paramType == null) { paramType = unknownType; } defineSlot(astParameter, functionNode, paramType, inferred); if (jsDocParameter != null) { jsDocParameter = jsDocParameter.getNext(); } if (iifeArgumentNode != null) { iifeArgumentNode = iifeArgumentNode.getNext(); } } } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> // end declareArguments } // end LocalScopeBuilder /** * Does a first-order function analysis that just looks at simple things * like what variables are escaped, and whether 'this' is used. */ private static class FirstOrderFunctionAnalyzer extends AbstractScopedCallback implements CompilerPass { private final AbstractCompiler compiler; private final Map<Node, AstFunctionContents> data; FirstOrderFunctionAnalyzer( AbstractCompiler compiler, Map<Node, AstFunctionContents> outParam) { this.compiler = compiler; this.data = outParam; } @Override public void process(Node externs, Node root) { if (externs == null) { NodeTraversal.traverse(compiler, root, this); } else { NodeTraversal.traverseRoots( compiler, ImmutableList.of(externs, root), this); } } @Override public void enterScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); data.put(n, new AstFunctionContents(n)); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (t.inGlobalScope()) { return; } if (n.isReturn() && n.getFirstChild() != null) { data.get(t.getScopeRoot()).recordNonEmptyReturn(); } if (t.getScopeDepth() <= 1) { // The first-order function analyzer looks at two types of variables: // // 1) Local variables that are assigned in inner scopes ("escaped vars") // // 2) Local variables that are assigned more than once. // // We treat all global variables as escaped by default, so there's // no reason to do this extra computation for them. return; } if (n.isName() && NodeUtil.isLValue(n) && // Be careful of bleeding functions, which create variables // in the inner scope, not the scope where the name appears. !NodeUtil.isBleedingFunctionName(n)) { String name = n.getString(); Scope scope = t.getScope(); Var var = scope.getVar(name); if (var != null) { Scope ownerScope = var.getScope(); if (ownerScope.isLocal()) { data.get(ownerScope.getRootNode()).recordAssignedName(name); } if (scope != ownerScope && ownerScope.isLocal()) { data.get(ownerScope.getRootNode()).recordEscapedVarName(name); } } } else if (n.isGetProp() && n.isUnscopedQualifiedName() && NodeUtil.isLValue(n)) { String name = NodeUtil.getRootOfQualifiedName(n).getString(); Scope scope = t.getScope(); Var var = scope.getVar(name); if (var != null) { Scope ownerScope = var.getScope(); if (scope != ownerScope && ownerScope.isLocal()) { data.get(ownerScope.getRootNode()) .recordEscapedQualifiedName(n.getQualifiedName()); } } } } } private AstFunctionContents getFunctionAnalysisResults(@Nullable Node

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.*; /** * Verifies that constants are only assigned a value once. * e.g. var XX = 5; * XX = 3; // error! * XX++; // error! * */ class ConstCheck extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR = DiagnosticType.error( "JSC_CONSTANT_REASSIGNED_VALUE_ERROR", "constant {0} assigned a value more than once"); private final AbstractCompiler compiler; private final Set<Scope.Var> initializedConstants; /** * Creates an instance. */ public ConstCheck(AbstractCompiler compiler) { this.compiler = compiler; this.initializedConstants = new HashSet<Scope.Var>(); } @Override public void process(Node externs, Node root) { Preconditions.checkState(compiler.getLifeCycleStage().isNormalized()); NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: if (parent != null && parent.isVar() && n.hasChildren()) { String name = n.getString(); Scope.Var var = t.getScope().getVar(name); if (isConstant(var)) { if (initializedConstants.contains(var)) { reportError(t, n, name); } else { initializedConstants.add(var); } } } break; case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: { Node lhs = n.getFirstChild(); if (lhs.isName()) { String name = lhs.getString(); Scope.Var var = t.getScope().getVar(name); if (isConstant(var)) { if (

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>initializedConstants.contains(var)) { reportError(t, n, name); } else { initializedConstants.add(var); } } } break; } case Token.INC: case Token.DEC: { Node lhs = n.getFirstChild(); if (lhs.isName()) { String name = lhs.getString(); Scope.Var var = t.getScope().getVar(name); if (isConstant(var)) { reportError(t, n, name); } } break; } } } /** * Gets whether a variable is a constant initialized to a literal value at * the point where it is declared. */ private boolean isConstant(Scope.Var var) { return var != null && var.isConst(); } /** * Reports a reassigned constant error. */ void reportError(NodeTraversal t, Node n, String name) { compiler.report(t.makeError(n, CONST_REASSIGNED_VALUE_ERROR, name)); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> rename = nameGenerator.getName(instanceId, name); } idGeneratorMap.put(rename, instanceId); return rename; } } /** * @return The serialize map of generators and their ids and their * replacements. */ public String getSerializedIdMappings() { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, Map<String, String>> replacements : idGeneratorMaps.entrySet()) { if (!replacements.getValue().isEmpty()) { sb.append("["); sb.append(replacements.getKey()); sb.append("]\n\n"); for (Map.Entry<String, String> replacement : replacements.getValue().entrySet()) { sb.append(replacement.getKey()); sb.append(':'); sb.append(replacement.getValue()); sb.append("\n"); } sb.append("\n"); } } return sb.toString(); } private Map<String, BiMap<String, String>> parsePreviousResults( String serializedMap) { // // The expected format looks like this: // // [generatorName] // someId:someFile:theLine:theColumn // // if (serializedMap == null || serializedMap.isEmpty()) { return Collections.emptyMap(); } Map<String, BiMap<String, String>> resultMap = Maps.newHashMap(); BufferedReader reader = new BufferedReader(new StringReader(serializedMap)); BiMap<String, String> currentSectionMap = null; String line; int lineIndex = 0; try { while ((line = reader.readLine()) != null) { lineIndex++; if (line.isEmpty()) { continue; } if (line.charAt(0) == '[') { String currentSection = line.substring(1, line.length() - 1); currentSectionMap = resultMap.get(currentSection); if (currentSectionMap == null) { currentSectionMap = HashBiMap.create(); resultMap.put(currentSection, currentSectionMap); } else { reportInvalidLine(line, lineIndex); return Collections.emptyMap(); } } else { int split = line.indexOf(':'); if (split != -1) { String name = line.substring(0, split); String location = line.substring(split + 1, line.length()); currentSectionMap.put(name, location); } else { reportInvalidLine(line, lineIndex); return Collections.emptyMap(); } } } } catch (IOException e) { JSError.make(INVALID_GENERATOR_ID_MAPPING, e.getMessage()); } return resultMap; } private void reportInvalidLine(String line, int lineIndex) { JSError.make(INVALID_GENERATOR_ID_MAPPING, "line(" + line + "): " + lineIndex); } String getIdForGeneratorNode(boolean consistent, Node n) { Preconditions.checkState(n.isString() || n.isStringKey()); if (consistent) { return n.getString(); } else { return n.getSourceFileName() + ':' + n.getLineno() + ":" +

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> tracked by MemoizedScopeCreator */ static class MemoizedScopeCleanupPass implements HotSwapCompilerPass { private final AbstractCompiler compiler; public MemoizedScopeCleanupPass(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { ScopeCreator creator = compiler.getTypedScopeCreator(); if (creator instanceof MemoizedScopeCreator) { MemoizedScopeCreator scopeCreator = (MemoizedScopeCreator) creator; String newSrc = scriptRoot.getSourceFileName(); for (Var var : scopeCreator.getAllSymbols()) { JSType type = var.getType(); if (type != null) { FunctionType fnType = type.toMaybeFunctionType(); if (fnType != null && newSrc.equals(NodeUtil.getSourceName(fnType.getSource()))) { fnType.setSource(null); } } } scopeCreator.removeScopesForScript(originalRoot.getSourceFileName()); } } @Override public void process(Node externs, Node root) { // MemoizedScopeCleanupPass should not do work during process. } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> SubclassRelationship getClassesDefinedByCall(Node callNode) { return nextConvention.getClassesDefinedByCall(callNode); } @Override public boolean isSuperClassReference(String propertyName) { return nextConvention.isSuperClassReference(propertyName); } @Override public String extractClassNameIfProvide(Node node, Node parent) { return nextConvention.extractClassNameIfProvide(node, parent); } @Override public String extractClassNameIfRequire(Node node, Node parent) { return nextConvention.extractClassNameIfRequire(node, parent); } @Override public String getExportPropertyFunction() { return nextConvention.getExportPropertyFunction(); } @Override public String getExportSymbolFunction() { return nextConvention.getExportSymbolFunction(); } @Override public List<String> identifyTypeDeclarationCall(Node n) { return nextConvention.identifyTypeDeclarationCall(n); } @Override public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type) { nextConvention.applySubclassRelationship( parentCtor, childCtor, type); } @Override public String getAbstractMethodName() { return nextConvention.getAbstractMethodName(); } @Override public String getSingletonGetterClassName(Node callNode) { return nextConvention.getSingletonGetterClassName(callNode); } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { nextConvention.applySingletonGetter( functionType, getterType, objectType); } @Override public boolean isInlinableFunction(Node n) { return nextConvention.isInlinableFunction(n); } @Override public DelegateRelationship getDelegateRelationship(Node callNode) { return nextConvention.getDelegateRelationship(callNode); } @Override public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate) { nextConvention.applyDelegateRelationship( delegateSuperclass, delegateBase, delegator, delegateProxy, findDelegate); } @Override public String getDelegateSuperclassName() { return nextConvention.getDelegateSuperclassName(); } @Override public void checkForCallingConventionDefiningCalls( Node n, Map<String, String> delegateCallingConventions) { nextConvention.checkForCallingConventionDefiningCalls( n, delegateCallingConventions); } @Override public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes, Map<String, String> delegateCallingConventions) { nextConvention.defineDelegateProxyPrototypeProperties( registry, scope, delegateProxyPrototypes, delegateCallingConventions); } @Override public String getGlobalObject() { return nextConvention.getGlobalObject(); } @Override public Collection<AssertionFunctionSpec> getAssertionFunctions() { return nextConvention.getAssertionFunctions(); } @Override public Bind describeFunctionBind(Node n) { return describeFunctionBind(n, false); } @Override public Bind describeFunctionBind(Node n, boolean useTypeInfo) { return nextConvention.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>) { return null; } @Override public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate) { // do nothing. } @Override public String getDelegateSuperclassName() { return null; } @Override public void checkForCallingConventionDefiningCalls(Node n, Map<String, String> delegateCallingConventions) { // do nothing. } @Override public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes, Map<String, String> delegateCallingConventions) { // do nothing. } @Override public String getGlobalObject() { return "window"; } @Override public boolean isPropertyTestFunction(Node call) { return false; } @Override public boolean isPrototypeAlias(Node getProp) { return false; } @Override public ObjectLiteralCast getObjectLiteralCast(Node callNode) { return null; } @Override public Collection<AssertionFunctionSpec> getAssertionFunctions() { return Collections.emptySet(); } @Override public Bind describeFunctionBind(Node n) { return describeFunctionBind(n, false); } @Override public Bind describeFunctionBind(Node n, boolean useTypeInfo) { if (!n.isCall()) { return null; } Node callTarget = n.getFirstChild(); String name = callTarget.getQualifiedName(); if (name != null) { if (name.equals("Function.prototype.bind.call")) { // goog.bind(fn, self, args...); Node fn = callTarget.getNext(); if (fn == null) { return null; } Node thisValue = safeNext(fn); Node parameters = safeNext(thisValue); return new Bind(fn, thisValue, parameters); } } if (callTarget.isGetProp() && callTarget.getLastChild().getString().equals("bind")) { Node maybeFn = callTarget.getFirstChild(); JSType maybeFnType = maybeFn.getJSType(); FunctionType fnType = null; if (useTypeInfo && maybeFnType != null) { fnType = maybeFnType.restrictByNotNullOrUndefined() .toMaybeFunctionType(); } if (fnType != null || maybeFn.isFunction()) { // (function(){}).bind(self, args...); Node thisValue = callTarget.getNext(); Node parameters = safeNext(thisValue); return new Bind(maybeFn, thisValue, parameters); } } return null; } @Override public Collection<String> getIndirectlyDeclaredProperties() { return ImmutableList.of(); } private Node safeNext(Node n) { if (n != null) { return n.getNext(); } return null; } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> line = getLineOfOffset(offset); return offset - lineOffsets[line - 1]; } /** * Gets the source line for the indicated line number. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Does not include the newline at the end * of the file. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public String getLine(int lineNumber) { findLineOffsets(); if (lineNumber > lineOffsets.length) { return null; } if (lineNumber < 1) { lineNumber = 1; } int pos = lineOffsets[lineNumber - 1]; String js = ""; try { // NOTE(nicksantos): Right now, this is optimized for few warnings. // This is probably the right trade-off, but will be slow if there // are lots of warnings in one file. js = getCode(); } catch (IOException e) { return null; } if (js.indexOf('\n', pos) == -1) { // If next new line cannot be found, there are two cases // 1. pos already reaches the end of file, then null should be returned // 2. otherwise, return the contents between pos and the end of file. if (pos >= js.length()) { return null; } else { return js.substring(pos, js.length()); } } else { return js.substring(pos, js.indexOf('\n', pos)); } } /** * Get a region around the indicated line number. The exact definition of a * region is implementation specific, but it must contain the line indicated * by the line number. A region must not start or end by a carriage return. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public Region getRegion(int lineNumber) { String js = ""; try { js = getCode(); } catch (IOException e) { return null; } int pos = 0; int startLine = Math.max(1, lineNumber - (SOURCE_EXCERPT_REGION_LENGTH + 1) / 2 + 1); for (int n = 1; n < startLine; n++) { int nextpos = js.indexOf('\n', pos); if (nextpos == -1) { break; } pos = nextpos + 1; } int end = pos; int endLine = startLine; for (int n = 0; n < SOURCE_EXCERPT_REGION_LENGTH; n++, endLine++) { end = js.indexOf('\n', end); if (end == -1) { break; } end++; } if (lineNumber >= endLine) { return null; } if (end == -1) { int last = js.length() - 1; if (js.charAt(last)

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> == '\n') { return new SimpleRegion(startLine, endLine, js.substring(pos, last)); } else { return new SimpleRegion(startLine, endLine, js.substring(pos)); } } else { return new SimpleRegion(startLine, endLine, js.substring(pos, end)); } } @Override public String toString() { return fileName; } public static SourceFile fromFile(String fileName, Charset c) { return builder().withCharset(c).buildFromFile(fileName); } public static SourceFile fromFile(String fileName) { return builder().buildFromFile(fileName); } public static SourceFile fromFile(File file, Charset c) { return builder().withCharset(c).buildFromFile(file); } public static SourceFile fromFile(File file) { return builder().buildFromFile(file); } public static SourceFile fromCode(String fileName, String code) { return builder().buildFromCode(fileName, code); } public static SourceFile fromCode(String fileName, String originalPath, String code) { return builder().withOriginalPath(originalPath) .buildFromCode(fileName, code); } public static SourceFile fromInputStream(String fileName, InputStream s) throws IOException { return builder().buildFromInputStream(fileName, s); } public static SourceFile fromInputStream(String fileName, String originalPath, InputStream s) throws IOException { return builder().withOriginalPath(originalPath) .buildFromInputStream(fileName, s); } public static SourceFile fromReader(String fileName, Reader r) throws IOException { return builder().buildFromReader(fileName, r); } public static SourceFile fromGenerator(String fileName, Generator generator) { return builder().buildFromGenerator(fileName, generator); } /** Create a new builder for source files. */ public static Builder builder() { return new Builder(); } /** * A builder interface for source files. * * Allows users to customize the Charset, and the original path of * the source file (if it differs from the path on disk). */ public static class Builder { private Charset charset = Charsets.UTF_8; private String originalPath = null; public Builder() {} /** Set the charset to use when reading from an input stream or file. */ public Builder withCharset(Charset charset) { this.charset = charset; return this; } /** Set the original path to use. */ public Builder withOriginalPath(String originalPath) { this.originalPath = originalPath; return this; } public SourceFile buildFromFile(String fileName) { return buildFromFile(new File(fileName)); } public SourceFile buildFromFile(File file) { return new OnDisk(file, originalPath, charset); } public SourceFile buildFromCode(String fileName, String code) { return new Preloaded(fileName, originalPath, code); } public SourceFile buildFromInputStream(String fileName, InputStream s) throws IOException { return buildFromCode(fileName, CharStreams.toString(new InputStreamReader(s, charset))); } public SourceFile buildFromReader(String fileName, Reader r)

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>, // so we'll allow it. if (n.isEmpty() || n.isComma()) { return; } if (parent == null) { return; } // Do not try to remove a block or an expr result. We already handle // these cases when we visit the child, and the peephole passes will // fix up the tree in more clever ways when these are removed. if (n.isExprResult() || n.isBlock()) { return; } // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. if (n.isQualifiedName() && n.getJSDocInfo() != null) { return; } boolean isResultUsed = NodeUtil.isExpressionResultUsed(n); boolean isSimpleOp = NodeUtil.isSimpleOperator(n); if (!isResultUsed && (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) { String msg = "This code lacks side-effects. Is there a bug?"; if (n.isString()) { msg = "Is there a missing '+' on the previous line?"; } else if (isSimpleOp) { msg = "The result of the '" + Token.name(n.getType()).toLowerCase() + "' operator is not being used."; } t.getCompiler().report( t.makeError(n, level, USELESS_CODE_ERROR, msg)); // TODO(johnlenz): determine if it is necessary to // try to protect side-effect free statements as well. if (!NodeUtil.isStatement(n)) { problemNodes.add(n); } } } /** * Protect side-effect free nodes by making them parameters * to a extern function call. This call will be removed * after all the optimizations passes have run. */ private void protectSideEffects() { if (!problemNodes.isEmpty()) { addExtern(); for (Node n : problemNodes) { Node name = IR.name(PROTECTOR_FN).srcref(n); name.putBooleanProp(Node.IS_CONSTANT_NAME, true); Node replacement = IR.call(name).srcref(n); replacement.putBooleanProp(Node.FREE_CALL, true); n.getParent().replaceChild(n, replacement); replacement.addChildToBack(n); } compiler.reportCodeChange(); } } private void addExtern() { Node name = IR.name(PROTECTOR_FN); name.putBooleanProp(Node.IS_CONSTANT_NAME, true); Node var = IR.var(name); // Add "@noalias" so we can strip the method when AliasExternals is enabled. JSDocInfoBuilder builder = new JSDocInfoBuilder(false); builder.recordNoAlias(); var.setJSDocInfo(builder.build(var)); CompilerInput input = compiler.getSynthesizedExternsInput(); input.getAstRoot(compiler).addChildrenToBack(var); compiler.reportCodeChange(); } /** * Remove

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2007 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.Node; import java.util.regex.Pattern; /** * This describes the Google-specific JavaScript coding conventions. * Within Google, variable names are semantically significant. * */ public class GoogleCodingConvention extends CodingConventions.Proxy { private static final long serialVersionUID = 1L; private static final String OPTIONAL_ARG_PREFIX = "opt_"; private static final String VAR_ARGS_NAME = "var_args"; private static final Pattern ENUM_KEY_PATTERN = Pattern.compile("[A-Z0-9][A-Z0-9_]*"); /** By default, decorate the ClosureCodingConvention. */ public GoogleCodingConvention() { this(new ClosureCodingConvention()); } /** Decorates a wrapped CodingConvention. */ public GoogleCodingConvention(CodingConvention convention) { super(convention); } /** * {@inheritDoc} * * <p>This enforces the Google const name convention, that the first character * after the last $ must be an upper-case letter and all subsequent letters * must be upper case. The name must be at least 2 characters long. * * <p>Examples: * <pre> * aaa Not constant - lower-case letters in the name * A Not constant - too short * goog$A Constant - letters after the $ are upper-case. * AA17 Constant - digits can appear after the first letter * goog$7A Not constant - first character after the $ must be * upper case. * $A Constant - doesn't have to be anything in front of the $ * </pre> */ @Override public boolean isConstant(String name) { if (name.length() <= 1) { return false; } // In compiled code, '$' is often a namespace delimiter. To allow inlining // of namespaced constants, we strip off any namespaces here. int pos = name.lastIndexOf('$'); if (pos >= 0) { name = name.substring(pos + 1); if (name.length() == 0) { return false; } } return isConstantKey(name); } @Override public boolean isConstantKey(String name) { if (name.isEmpty() || !Character.isUpperCase(name.charAt(0))) { return false; } // hack way of checking that there aren't any lower-case

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Value.UNKNOWN) { return val.toBoolean(true) == (Branch.ON_TRUE == branch); } } } return true; } }; /** * @param level level of severity to report when a missing return statement * is discovered */ CheckMissingReturn(AbstractCompiler compiler, CheckLevel level) { this.compiler = compiler; this.level = level; } @Override public void enterScope(NodeTraversal t) { JSType returnType = explicitReturnExpected(t.getScopeRoot()); if (returnType == null) { return; } if (fastAllPathsReturnCheck(t.getControlFlowGraph())) { return; } CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch> test = new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>( t.getControlFlowGraph(), t.getControlFlowGraph().getEntry(), t.getControlFlowGraph().getImplicitReturn(), IS_RETURN, GOES_THROUGH_TRUE_CONDITION_PREDICATE); if (!test.allPathsSatisfyPredicate()) { compiler.report( t.makeError(t.getScopeRoot(), level, MISSING_RETURN_STATEMENT, returnType.toString())); } } /** * Fast check to see if all execution paths contain a return statement. * May spuriously report that a return statement is missing. * * @return true if all paths return, converse not necessarily true */ private static boolean fastAllPathsReturnCheck(ControlFlowGraph<Node> cfg) { for (DiGraphEdge<Node, Branch> s : cfg.getImplicitReturn().getInEdges()) { if (!s.getSource().getValue().isReturn()) { return false; } } return true; } @Override public void exitScope(NodeTraversal t) { } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { } /** * Determines if the given scope should explicitly return. All functions * with non-void or non-unknown return types must have explicit returns. * * Exception: Constructors which specifically specify a return type are * used to allow invocation without requiring the "new" keyword. They * have an implicit return type. See unit tests. * * @return If a return type is expected, returns it. Otherwise, returns null. */ private JSType explicitReturnExpected(Node scope) { FunctionType scopeType = JSType.toMaybeFunctionType(scope.getJSType()); if (scopeType == null) { return null; } if (isEmptyFunction(scope)) { return null; } if (scopeType.isConstructor()) { return null; } JSType returnType = scopeType.getReturnType(); if (returnType == null) { return null; } if (!isVoidOrUnknown(returnType)) { return returnType; } return null; } /** * @return {@code true} if function represents a JavaScript function * with an empty body */ private static boolean isEmptyFunction(Node

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.javascript.jscomp.Scope.Var; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.StaticSymbolTable; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; /** * Memoize a scope creator. * * This allows you to make multiple passes, without worrying about * the expense of generating Scope objects over and over again. * * On the other hand, you also have to be more aware of what your passes * are doing. Scopes are memoized stupidly, so if the underlying tree * changes, the scope may be out of sync. * * @author nicksantos@google.com (Nick Santos) */ class MemoizedScopeCreator implements ScopeCreator, StaticSymbolTable<Var, Var> { private final Map<Node, Scope> scopes = Maps.newHashMap(); private final ScopeCreator delegate; /** * @param delegate The real source of Scope objects. */ MemoizedScopeCreator(ScopeCreator delegate) { this.delegate = delegate; } @Override public Iterable<Var> getReferences(Var var) { return ImmutableList.of(var); } @Override public Scope getScope(Var var) { return var.scope; } @Override public Iterable<Var> getAllSymbols() { List<Var> vars = Lists.newArrayList(); for (Scope s : scopes.values()) { Iterables.addAll(vars, s.getAllSymbols()); } return vars; } @Override public Scope createScope(Node n, Scope parent) { Scope scope = scopes.get(n); if (scope == null) { scope = delegate.createScope(n, parent); scopes.put(n, scope); } else { Preconditions.checkState(parent == scope.getParent()); } return scope; } Collection<Scope> getAllMemoizedScopes() { return Collections.unmodifiableCollection(scopes.values()); } Scope getScopeIfMemoized(Node n) { return

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n, c) && !c.isBlock()) { Node newBlock = IR.block().srcref(n); n.replaceChild(c, newBlock); if (!c.isEmpty()) { newBlock.addChildrenToFront(c); } else { newBlock.setWasEmptyNode(true); } c = newBlock; reportChange(); } } } } /** * Normalize where annotations appear on the AST. Copies * around existing JSDoc annotations as well as internal annotations. */ static class PrepareAnnotations implements NodeTraversal.Callback { PrepareAnnotations() { } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.isObjectLit()) { normalizeObjectLiteralAnnotations(n); } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: annotateCalls(n); break; case Token.FUNCTION: annotateDispatchers(n, parent); break; } } private void normalizeObjectLiteralAnnotations(Node objlit) { Preconditions.checkState(objlit.isObjectLit()); for (Node key = objlit.getFirstChild(); key != null; key = key.getNext()) { Node value = key.getFirstChild(); normalizeObjectLiteralKeyAnnotations(objlit, key, value); } } /** * There are two types of calls we are interested in calls without explicit * "this" values (what we are call "free" calls) and direct call to eval. */ private void annotateCalls(Node n) { Preconditions.checkState(n.isCall()); // Keep track of of the "this" context of a call. A call without an // explicit "this" is a free call. Node first = n.getFirstChild(); // ignore cast nodes. while (first.isCast()) { first = first.getFirstChild(); } if (!NodeUtil.isGet(first)) { n.putBooleanProp(Node.FREE_CALL, true); } // Keep track of the context in which eval is called. It is important // to distinguish between "(0, eval)()" and "eval()". if (first.isName() && "eval".equals(first.getString())) { first.putBooleanProp(Node.DIRECT_EVAL, true); } } /** * Translate dispatcher info into the property expected node. */ private void annotateDispatchers(Node n, Node parent) { Preconditions.checkState(n.isFunction()); if (parent.getJSDocInfo() != null && parent.getJSDocInfo().isJavaDispatch()) { if (parent.isAssign()) { Preconditions.checkState(parent.getLastChild() == n); n.putBooleanProp(Node.IS_DISPATCHER, true); } } } /** * In the AST that Rhino gives us, it needs to make a distinction * between JsDoc on the object literal node

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> n, JSType rightType, JSType leftType, Node owner, String propName) { // The NoType check is a hack to make typedefs work OK. if (!leftType.isNoType() && !rightType.isSubtype(leftType)) { // Do not type-check interface methods, because we expect that // they will have dummy implementations that do not match the type // annotations. JSType ownerType = getJSType(owner); if (ownerType.isFunctionPrototypeType()) { FunctionType ownerFn = ownerType.toObjectType().getOwnerFunction(); if (ownerFn.isInterface() && rightType.isFunctionType() && leftType.isFunctionType()) { return true; } } mismatch(t, n, "assignment to property " + propName + " of " + getReadableJSTypeName(owner, true), rightType, leftType); return false; } return true; } /** * Expect that the first type can be assigned to a symbol of the second * type. * * @param t The node traversal. * @param n The node to issue warnings on. * @param rightType The type on the RHS of the assign. * @param leftType The type of the symbol on the LHS of the assign. * @param msg An extra message for the mismatch warning, if necessary. * @return True if the types matched, false otherwise. */ boolean expectCanAssignTo(NodeTraversal t, Node n, JSType rightType, JSType leftType, String msg) { if (!rightType.isSubtype(leftType)) { mismatch(t, n, msg, rightType, leftType); return false; } return true; } /** * Expect that the type of an argument matches the type of the parameter * that it's fulfilling. * * @param t The node traversal. * @param n The node to issue warnings on. * @param argType The type of the argument. * @param paramType The type of the parameter. * @param callNode The call node, to help with the warning message. * @param ordinal The argument ordinal, to help with the warning message. */ void expectArgumentMatchesParameter(NodeTraversal t, Node n, JSType argType, JSType paramType, Node callNode, int ordinal) { if (!argType.isSubtype(paramType)) { mismatch(t, n, String.format("actual parameter %d of %s does not match " + "formal parameter", ordinal, getReadableJSTypeName(callNode.getFirstChild(), false)), argType, paramType); } } /** * Expect that the first type can override a property of the second * type. * * @param t The node traversal. * @param n The node to issue warnings on. * @param overridingType The overriding type. * @param hiddenType The type of the property being overridden. * @param propertyName The name of the property, for use in the * warning message. * @param ownerType The type of the owner of the property, for use * in the warning message. */ void expectCanOverride(NodeTraversal

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> new source info. */ Var expectUndeclaredVariable(String sourceName, CompilerInput input, Node n, Node parent, Var var, String variableName, JSType newType) { Var newVar = var; boolean allowDupe = false; if (n.isGetProp() || NodeUtil.isObjectLitKey(n)) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); } JSType varType = var.getType(); // Only report duplicate declarations that have types. Other duplicates // will be reported by the syntactic scope creator later in the // compilation process. if (varType != null && varType != typeRegistry.getNativeType(UNKNOWN_TYPE) && newType != null && newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) { // If there are two typed declarations of the same variable, that // is an error and the second declaration is ignored, except in the // case of native types. A null input type means that the declaration // was made in TypedScopeCreator#createInitialScope and is a // native type. We should redeclare it at the new input site. if (var.input == null) { Scope s = var.getScope(); s.undeclare(var); newVar = s.declare(variableName, n, varType, input, false); n.setJSType(varType); if (parent.isVar()) { if (n.getFirstChild() != null) { n.getFirstChild().setJSType(varType); } } else { Preconditions.checkState(parent.isFunction()); parent.setJSType(varType); } } else { // Always warn about duplicates if the overridden type does not // match the original type. // // If the types match, suppress the warning iff there was a @suppress // tag, or if the original declaration was a stub. if (!(allowDupe || var.getParentNode().isExprResult()) || !newType.isEquivalentTo(varType)) { report(JSError.make(sourceName, n, DUP_VAR_DECLARATION, variableName, newType.toString(), var.getInputName(), String.valueOf(var.nameNode.getLineno()), varType.toString())); } } } return newVar; } /** * Expect that all properties on interfaces that this type implements are * implemented and correctly typed. */ void expectAllInterfaceProperties(NodeTraversal t, Node n, FunctionType type) { ObjectType instance = type.getInstanceType(); for (ObjectType implemented : type.getAllImplementedInterfaces()) { if (implemented.getImplicitPrototype() != null) { for (String prop : implemented.getImplicitPrototype().getOwnPropertyNames()) { expectInterfaceProperty(t, n, instance, implemented, prop); } } } } /** * Expect that the property in an interface that this type implements is * implemented and correctly typed. */ private void expectInterfaceProperty(NodeTraversal

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> t, Node n, ObjectType instance, ObjectType implementedInterface, String prop) { StaticSlot<JSType> propSlot = instance.getSlot(prop); if (propSlot == null) { // Not implemented String sourceName = n.getSourceFileName(); sourceName = sourceName == null ? "" : sourceName; registerMismatch(instance, implementedInterface, report(JSError.make(sourceName, n, INTERFACE_METHOD_NOT_IMPLEMENTED, prop, implementedInterface.toString(), instance.toString()))); } else { Node propNode = propSlot.getDeclaration() == null ? null : propSlot.getDeclaration().getNode(); // Fall back on the constructor node if we can't find a node for the // property. propNode = propNode == null ? n : propNode; JSType found = propSlot.getType(); found = found.restrictByNotNullOrUndefined(); JSType required = implementedInterface.getImplicitPrototype().getPropertyType(prop); TemplateTypeMap typeMap = implementedInterface.getTemplateTypeMap(); if (!typeMap.isEmpty()) { TemplateTypeMapReplacer replacer = new TemplateTypeMapReplacer( typeRegistry, typeMap); required = required.visit(replacer); } required = required.restrictByNotNullOrUndefined(); if (!found.isSubtype(required)) { // Implemented, but not correctly typed FunctionType constructor = implementedInterface.toObjectType().getConstructor(); registerMismatch(found, required, report(t.makeError(propNode, HIDDEN_INTERFACE_PROPERTY_MISMATCH, prop, constructor.getTopMostDefiningType(prop).toString(), required.toString(), found.toString()))); } } } /** * Report a type mismatch */ private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSType required) { mismatch(t.getSourceName(), n, msg, found, required); } private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSTypeNative required) { mismatch(t, n, msg, found, getNativeType(required)); } private void mismatch(String sourceName, Node n, String msg, JSType found, JSType required) { registerMismatch(found, required, report( JSError.make(sourceName, n, TYPE_MISMATCH_WARNING, formatFoundRequired(msg, found, required)))); } private void registerMismatch(JSType found, JSType required, JSError error) { // Don't register a mismatch for differences in null or undefined or if the // code didn't downcast. found = found.restrictByNotNullOrUndefined(); required = required.restrictByNotNullOrUndefined(); if (found.isSubtype(required) || required.isSubtype(found)) { return; } mismatches.add(new TypeMismatch(found, required, error)); if (found.isFunctionType() && required.isFunctionType()) { FunctionType fnTypeA = found.toMaybeFunctionType(); FunctionType fnTypeB = required.toMaybeFunctionType(); Iterator<Node> paramItA = fnTypeA.getParameters().iterator(); Iterator<Node> paramItB = fnTypeB.getParameters().iterator();

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> while (paramItA.hasNext() && paramItB.hasNext()) { registerIfMismatch(paramItA.next().getJSType(), paramItB.next().getJSType(), error); } registerIfMismatch( fnTypeA.getReturnType(), fnTypeB.getReturnType(), error); } } private void registerIfMismatch( JSType found, JSType required, JSError error) { if (found != null && required != null && !found.isSubtype(required)) { registerMismatch(found, required, error); } } /** * Formats a found/required error message. */ private String formatFoundRequired(String description, JSType found, JSType required) { return MessageFormat.format(FOUND_REQUIRED, description, found, required); } /** * Given a node, get a human-readable name for the type of that node so * that will be easy for the programmer to find the original declaration. * * For example, if SubFoo's property "bar" might have the human-readable * name "Foo.prototype.bar". * * @param n The node. * @param dereference If true, the type of the node will be dereferenced * to an Object type, if possible. */ String getReadableJSTypeName(Node n, boolean dereference) { // If we're analyzing a GETPROP, the property may be inherited by the // prototype chain. So climb the prototype chain and find out where // the property was originally defined. if (n.isGetProp()) { ObjectType objectType = getJSType(n.getFirstChild()).dereference(); if (objectType != null) { String propName = n.getLastChild().getString(); if (objectType.getConstructor() != null && objectType.getConstructor().isInterface()) { objectType = FunctionType.getTopDefiningInterface( objectType, propName); } else { // classes while (objectType != null && !objectType.hasOwnProperty(propName)) { objectType = objectType.getImplicitPrototype(); } } // Don't show complex function names or anonymous types. // Instead, try to get a human-readable type name. if (objectType != null && (objectType.getConstructor() != null || objectType.isFunctionPrototypeType())) { return objectType.toString() + "." + propName; } } } JSType type = getJSType(n); if (dereference) { ObjectType dereferenced = type.dereference(); if (dereferenced != null) { type = dereferenced; } } String qualifiedName = n.getQualifiedName(); if (type.isFunctionPrototypeType() || (type.toObjectType() != null && type.toObjectType().getConstructor() != null)) { return type.toString(); } else if (qualifiedName != null) { return qualifiedName; } else if (type.isFunctionType()) { // Don't show complex function names. return "function"; } else { return type.toString(); } } /** * This method gets the JSType from the Node argument and verifies that

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { return null; } return p.typeA.getTypesUnderShallowEquality(p.typeB); } }; /** * Merging function for strict non-equality between types. */ private static final Function<TypePair, TypePair> SHNE = new Function<TypePair, TypePair>() { @Override public TypePair apply(TypePair p) { if (p.typeA == null || p.typeB == null) { return null; } return p.typeA.getTypesUnderShallowInequality(p.typeB); } }; /** * Merging function for inequality comparisons between types. */ private final Function<TypePair, TypePair> ineq = new Function<TypePair, TypePair>() { @Override public TypePair apply(TypePair p) { return new TypePair( getRestrictedWithoutUndefined(p.typeA), getRestrictedWithoutUndefined(p.typeB)); } }; /** * Creates a semantic reverse abstract interpreter. */ public SemanticReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { super(convention, typeRegistry); } @Override public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { // Check for the typeof operator. int operatorToken = condition.getType(); switch (operatorToken) { case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.CASE: Node left; Node right; if (operatorToken == Token.CASE) { left = condition.getParent().getFirstChild(); // the switch condition right = condition.getFirstChild(); } else { left = condition.getFirstChild(); right = condition.getLastChild(); } Node typeOfNode = null; Node stringNode = null; if (left.isTypeOf() && right.isString()) { typeOfNode = left; stringNode = right; } else if (right.isTypeOf() && left.isString()) { typeOfNode = right; stringNode = left; } if (typeOfNode != null && stringNode != null) { Node operandNode = typeOfNode.getFirstChild(); JSType operandType = getTypeIfRefinable(operandNode, blindScope); if (operandType != null) { boolean resultEqualsValue = operatorToken == Token.EQ || operatorToken == Token.SHEQ || operatorToken == Token.CASE; if (!outcome) { resultEqualsValue = !resultEqualsValue; } return caseTypeOf(operandNode, operandType, stringNode.getString(), resultEqualsValue, blindScope); } } } switch (operatorToken) { case Token.AND: if (outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } case Token.OR: if (!outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLast

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS><JSType> rightVar = rightScope.findUniqueRefinedSlot(blindScope); if (rightVar == null || !leftVar.getName().equals(rightVar.getName())) { return blindScope == rightScope ? blindScope : blindScope.createChildFlowScope(); } JSType type = leftVar.getType().getLeastSupertype(rightVar.getType()); FlowScope informed = blindScope.createChildFlowScope(); informed.inferSlotType(leftVar.getName(), type); return informed; } /** * If the restrictedType differs from the originalType, then we should * branch the current flow scope and create a new flow scope with the name * declared with the new type. * * We try not to create spurious child flow scopes as this makes type * inference slower. * * We also do not want spurious slots around in type inference, because * we use these as a signal for "checked unknown" types. A "checked unknown" * type is a symbol that the programmer has already checked and verified that * it's defined, even if we don't know what it is. * * It is OK to pass non-name nodes into this method, as long as you pass * in {@code null} for a restricted type. */ private FlowScope maybeRestrictName( FlowScope blindScope, Node node, JSType originalType, JSType restrictedType) { if (restrictedType != null && restrictedType != originalType) { FlowScope informed = blindScope.createChildFlowScope(); declareNameInScope(informed, node, restrictedType); return informed; } return blindScope; } /** * @see #maybeRestrictName */ private FlowScope maybeRestrictTwoNames( FlowScope blindScope, Node left, JSType originalLeftType, JSType restrictedLeftType, Node right, JSType originalRightType, JSType restrictedRightType) { boolean shouldRefineLeft = restrictedLeftType != null && restrictedLeftType != originalLeftType; boolean shouldRefineRight = restrictedRightType != null && restrictedRightType != originalRightType; if (shouldRefineLeft || shouldRefineRight) { FlowScope informed = blindScope.createChildFlowScope(); if (shouldRefineLeft) { declareNameInScope(informed, left, restrictedLeftType); } if (shouldRefineRight) { declareNameInScope(informed, right, restrictedRightType); } return informed; } return blindScope; } private FlowScope caseNameOrGetProp(Node name, FlowScope blindScope, boolean outcome) { JSType type = getTypeIfRefinable(name, blindScope); if (type != null) { return maybeRestrictName( blindScope, name, type, type.getRestrictedTypeGivenToBooleanOutcome(outcome)); } return blindScope; } private FlowScope caseTypeOf(Node node, JSType type, String value, boolean resultEqualsValue, FlowScope blindScope) { return maybeRestrictName( blindScope, node, type, getRestrictedByTypeOfResult(type, value, resultEqualsValue)); } private FlowScope caseInstanceOf

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(Node left, Node right, FlowScope blindScope, boolean outcome) { JSType leftType = getTypeIfRefinable(left, blindScope); if (leftType == null) { return blindScope; } JSType rightType = right.getJSType(); ObjectType targetType = typeRegistry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE); if (rightType != null && rightType.isFunctionType()) { targetType = rightType.toMaybeFunctionType(); } Visitor<JSType> visitor; if (outcome) { visitor = new RestrictByTrueInstanceOfResultVisitor(targetType); } else { visitor = new RestrictByFalseInstanceOfResultVisitor(targetType); } return maybeRestrictName( blindScope, left, leftType, leftType.visit(visitor)); } /** * Given 'property in object', ensures that the object has the property in the * informed scope by defining it as a qualified name if the object type lacks * the property and it's not in the blind scope. * @param object The node of the right-side of the in. * @param propertyName The string of the left-side of the in. */ private FlowScope caseIn(Node object, String propertyName, FlowScope blindScope) { JSType jsType = object.getJSType(); jsType = this.getRestrictedWithoutNull(jsType); jsType = this.getRestrictedWithoutUndefined(jsType); boolean hasProperty = false; ObjectType objectType = ObjectType.cast(jsType); if (objectType != null) { hasProperty = objectType.hasProperty(propertyName); } if (!hasProperty) { String qualifiedName = object.getQualifiedName(); if (qualifiedName != null) { String propertyQualifiedName = qualifiedName + "." + propertyName; if (blindScope.getSlot(propertyQualifiedName) == null) { FlowScope informed = blindScope.createChildFlowScope(); JSType unknownType = typeRegistry.getNativeType( JSTypeNative.UNKNOWN_TYPE); informed.inferQualifiedSlot( object, propertyQualifiedName, unknownType, unknownType); return informed; } } } return blindScope; } /** * @see SemanticReverseAbstractInterpreter#caseInstanceOf */ private class RestrictByTrueInstanceOfResultVisitor extends RestrictByTrueTypeOfResultVisitor { private final ObjectType target; RestrictByTrueInstanceOfResultVisitor(ObjectType target) { this.target = target; } @Override protected JSType caseTopType(JSType type) { return applyCommonRestriction(type); } @Override public JSType caseUnknownType() { FunctionType funcTarget = JSType.toMaybeFunctionType(target); if (funcTarget != null && funcTarget.hasInstanceType()) { return funcTarget.getInstanceType(); } return getNativeType(UNKNOWN_TYPE); } @Override public JSType caseObjectType(ObjectType type) { return applyCommonRestriction(type); } @Override public JSType caseUnionType(UnionType type) { return applyCommonRestriction(type); } @Override public JSType caseFunctionType(FunctionType type) { return caseObjectType(type

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> getStringFromBuffer() { tokenEnd = cursor; return new String(stringBuffer, 0, stringBufferTop); } private void addToString(int c) { int n = stringBufferTop; if (n == stringBuffer.length) { char[] tmp = new char[stringBuffer.length * 2]; System.arraycopy(stringBuffer, 0, tmp, 0, n); stringBuffer = tmp; } stringBuffer[n] = (char) c; stringBufferTop = n + 1; } void ungetChar(int c) { // can not unread past across line boundary assert(!(ungetCursor != 0 && ungetBuffer[ungetCursor - 1] == '\n')); ungetBuffer[ungetCursor++] = c; cursor--; } private boolean matchChar(int test) { int c = getCharIgnoreLineEnd(); if (c == test) { tokenEnd = cursor; return true; } else { ungetCharIgnoreLineEnd(c); return false; } } private static boolean isAlpha(int c) { // Use 'Z' < 'a' if (c <= 'Z') { return 'A' <= c; } else { return 'a' <= c && c <= 'z'; } } private boolean isJSDocString(int c) { switch (c) { case '@': case '*': case ',': case '>': case ':': case '(': case ')': case '{': case '}': case '[': case ']': case '?': case '!': case '|': case '=': case EOF_CHAR: case '\n': return false; default: return !isJSSpace(c); } } /* As defined in ECMA. jsscan.c uses C isspace() (which allows * \v, I think.) note that code in getChar() implicitly accepts * '\r' == \u000D as well. */ static boolean isJSSpace(int c) { if (c <= 127) { return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB; } else { return c == 0xA0 || Character.getType((char) c) == Character.SPACE_SEPARATOR; } } private static boolean isJSFormatChar(int c) { return c > 127 && Character.getType((char) c) == Character.FORMAT; } /** * Allows the JSDocParser to update the character offset * so that getCharno() returns a valid character position. */ void update() { charno = getOffset(); } private int peekChar() { int c = getChar(); ungetChar(c); return c; } protected int getChar() { if (ungetCursor != 0) { cursor++; --ungetCursor; if (charno == -1) { charno = getOffset(); } return ungetBuffer[ungetCursor]; }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>FunctionJsDocInfo(n); if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.isInterface() || jsDoc.hasThisType() || jsDoc.isOverride())) { return false; } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING_KEY)) { return false; } // Don't traverse functions that are getting lent to a prototype. Node gramps = parent.getParent(); if (NodeUtil.isObjectLitKey(parent)) { JSDocInfo maybeLends = gramps.getJSDocInfo(); if (maybeLends != null && maybeLends.getLendsName() != null && maybeLends.getLendsName().endsWith(".prototype")) { return false; } } } if (parent != null && parent.isAssign()) { Node lhs = parent.getFirstChild(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { // Only traverse the right side if it's not an assignment to a prototype // property or subproperty. if (NodeUtil.isGet(lhs)) { if (lhs.isGetProp() && lhs.getLastChild().getString().equals("prototype")) { return false; } Node llhs = lhs.getFirstChild(); if (llhs.isGetProp() && llhs.getLastChild().getString().equals("prototype")) { return false; } } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isThis() && shouldReportThis(n)) { compiler.report(t.makeError(n, GLOBAL_THIS)); } if (n == assignLhsChild) { assignLhsChild = null; } } private boolean shouldReportThis(Node n) { Node parent = n.getParent(); if (assignLhsChild != null) { // Always report a THIS on the left side of an assign. return true; } // Also report a THIS with a property access. return parent != null && NodeUtil.isGet(parent); } /** * Gets a function's JSDoc information, if it has any. Checks for a few * patterns (ellipses show where JSDoc would be): * <pre> * ... function() {} * ... x = function() {}; * var ...

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> x = function() {}; * ... var x = function() {}; * </pre> */ private JSDocInfo getFunctionJsDocInfo(Node n) { JSDocInfo jsDoc = n.getJSDocInfo(); Node parent = n.getParent(); if (jsDoc == null) { int parentType = parent.getType(); if (parentType == Token.NAME || parentType == Token.ASSIGN) { jsDoc = parent.getJSDocInfo(); if (jsDoc == null && parentType == Token.NAME) { Node gramps = parent.getParent(); if (gramps.isVar()) { jsDoc = gramps.getJSDocInfo(); } } } } return jsDoc; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> record. * @return The record type. */ public JSType build() { // If we have an empty record, simply return the object type. if (isEmpty) { return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE); } return new RecordType( registry, Collections.unmodifiableMap(properties), isDeclared); } static class RecordProperty { private final JSType type; private final Node propertyNode; RecordProperty(JSType type, Node propertyNode) { this.type = type; this.propertyNode = propertyNode; } public JSType getType() { return type; } public Node getPropertyNode() { return propertyNode; } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>JSC_NON_GLOBAL_DEFINE_INIT_ERROR", "@define variable {0} assignment must be global"); static final DiagnosticType DEFINE_NOT_ASSIGNABLE_ERROR = DiagnosticType.error( "JSC_DEFINE_NOT_ASSIGNABLE_ERROR", "@define variable {0} cannot be reassigned due to code at {1}."); private static final MessageFormat REASON_DEFINE_NOT_ASSIGNABLE = new MessageFormat("line {0} of {1}"); /** * Create a pass that overrides define constants. * * TODO(nicksantos): Write a builder to help JSCompiler induce * {@code replacements} from command-line flags * * @param replacements A hash table of names of defines to their replacements. * All replacements <b>must</b> be literals. */ ProcessDefines(AbstractCompiler compiler, Map<String, Node> replacements) { this.compiler = compiler; dominantReplacements = replacements; } /** * Injects a pre-computed global namespace, so that the same namespace * can be re-used for multiple check passes. Returns {@code this} for * easy chaining. */ ProcessDefines injectNamespace(GlobalNamespace namespace) { this.namespace = namespace; return this; } @Override public void process(Node externs, Node root) { if (namespace == null) { namespace = new GlobalNamespace(compiler, root); } overrideDefines(collectDefines(root, namespace)); } private void overrideDefines(Map<String, DefineInfo> allDefines) { boolean changed = false; for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) { String defineName = def.getKey(); DefineInfo info = def.getValue(); Node inputValue = dominantReplacements.get(defineName); Node finalValue = inputValue != null ? inputValue : info.getLastValue(); if (finalValue != info.initialValue) { info.initialValueParent.replaceChild( info.initialValue, finalValue.cloneTree()); compiler.addToDebugLog("Overriding @define variable " + defineName); changed = changed || finalValue.getType() != info.initialValue.getType() || !finalValue.isEquivalentTo(info.initialValue); } } if (changed) { compiler.reportCodeChange(); } Set<String> unusedReplacements = dominantReplacements.keySet(); unusedReplacements.removeAll(allDefines.keySet()); unusedReplacements.removeAll(KNOWN_DEFINES); for (String unknownDefine : unusedReplacements) { compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine)); } } private static String format(MessageFormat format, Object... params) { return format.format(params); } /** * Only defines of literal number, string, or boolean are supported. */ private boolean isValidDefineType(JSTypeExpression expression) { JSType type = expression.evaluate(null, compiler.getTypeRegistry()); return !type.isUnknownType() && type.isSubtype( compiler.getTypeRegistry().getNativeType( JSTypeNative.NUMBER_STRING_BOOLEAN

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>)); } /** * Finds all defines, and creates a {@link DefineInfo} data structure for * each one. * @return A map of {@link DefineInfo} structures, keyed by name. */ private Map<String, DefineInfo> collectDefines(Node root, GlobalNamespace namespace) { // Find all the global names with a @define annotation List<Name> allDefines = Lists.newArrayList(); for (Name name : namespace.getNameIndex().values()) { Ref decl = name.getDeclaration(); if (name.docInfo != null && name.docInfo.isDefine()) { // Process defines should not depend on check types being enabled, // so we look for the JSDoc instead of the inferred type. if (isValidDefineType(name.docInfo.getType())) { allDefines.add(name); } else { JSError error = JSError.make( decl.getSourceName(), decl.node, INVALID_DEFINE_TYPE_ERROR); compiler.report(error); } } else { for (Ref ref : name.getRefs()) { if (ref == decl) { // Declarations were handled above. continue; } Node n = ref.node; Node parent = ref.node.getParent(); JSDocInfo info = n.getJSDocInfo(); if (info == null && parent.isVar() && parent.hasOneChild()) { info = parent.getJSDocInfo(); } if (info != null && info.isDefine()) { allDefines.add(name); break; } } } } CollectDefines pass = new CollectDefines(compiler, allDefines); NodeTraversal.traverse(compiler, root, pass); return pass.getAllDefines(); } /** * Finds all assignments to @defines, and figures out the last value of * the @define. */ private static final class CollectDefines implements Callback { private final AbstractCompiler compiler; private final Map<String, DefineInfo> assignableDefines; private final Map<String, DefineInfo> allDefines; private final Map<Node, RefInfo> allRefInfo; // A hack that allows us to remove ASSIGN/VAR statements when // we're currently visiting one of the children of the assign. private Node lvalueToRemoveLater = null; // A stack tied to the node traversal, to keep track of whether // we're in a conditional block. If 1 is at the top, assignment to // a define is allowed. Otherwise, it's not allowed. private final Deque<Integer> assignAllowed; CollectDefines(AbstractCompiler compiler, List<Name> listOfDefines) { this.compiler = compiler; this.allDefines = Maps.newHashMap(); assignableDefines = Maps.newHashMap(); assignAllowed = new ArrayDeque<Integer>(); assignAllowed.push(1); // Create a map of references to defines keyed by node for easy lookup allRefInfo = Maps.newHashMap(); for (Name name : listOfDefines) { Ref decl = name.getDeclaration(); if (decl != null) { allRefInfo.put(decl.node, new RefInfo(decl, name)); } for (Ref ref : name

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(last); parent.replaceChild(n, last); } else { Preconditions.checkState(n.isName()); n.removeChild(n.getFirstChild()); } compiler.reportCodeChange(); } if (n.isCall()) { if (t.inGlobalScope()) { // If there's a function call in the global scope, // we just say it's unsafe and freeze all the defines. // // NOTE(nicksantos): We could be a lot smarter here. For example, // ReplaceOverriddenVars keeps a call graph of all functions and // which functions/variables that they reference, and tries // to statically determine which functions are "safe" and which // are not. But this would be overkill, especially because // the intended use of defines is with config_files, where // all the defines are at the top of the bundle. for (DefineInfo info : assignableDefines.values()) { setDefineInfoNotAssignable(info, t); } assignableDefines.clear(); } } updateAssignAllowedStack(n, false); } /** * Determines whether assignment to a define should be allowed * in the subtree of the given node, and if not, records that fact. * * @param n The node whose subtree we're about to enter or exit. * @param entering True if we're entering the subtree, false otherwise. */ private void updateAssignAllowedStack(Node n, boolean entering) { switch (n.getType()) { case Token.CASE: case Token.FOR: case Token.FUNCTION: case Token.HOOK: case Token.IF: case Token.SWITCH: case Token.WHILE: if (entering) { assignAllowed.push(0); } else { assignAllowed.remove(); } break; } } /** * Determines whether assignment to a define should be allowed * at the current point of the traversal. */ private boolean isAssignAllowed() { return assignAllowed.element() == 1; } /** * Tracks the given define. * * @param t The current traversal, for context. * @param name The full name for this define. * @param value The value assigned to the define. * @param valueParent The parent node of value. * @return Whether we should remove this assignment from the parse tree. */ private boolean processDefineAssignment(NodeTraversal t, String name, Node value, Node valueParent) { if (value == null || !NodeUtil.isValidDefineValue(value, allDefines.keySet())) { compiler.report( t.makeError(value, INVALID_DEFINE_INIT_ERROR, name)); } else if (!isAssignAllowed()) { compiler.report( t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name)); } else { DefineInfo info = allDefines.get(name); if (info == null) { // First declaration of this define. info = new DefineInfo(value, valueParent); allDefines.put(name, info); assignableDefines.put(name, info); } else if (info.recordAssignment(value)) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.javascript.rhino.jstype.TemplateType; import com.google.javascript.rhino.jstype.TemplateTypeMap; import com.google.javascript.rhino.jstype.TemplateTypeMapReplacer; import com.google.javascript.rhino.jstype.UnionType; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Type inference within a script node or a function body, using the data-flow * analysis framework. * */ class TypeInference extends DataFlowAnalysis.BranchedForwardDataFlowAnalysis<Node, FlowScope> { // TODO(johnlenz): We no longer make this check, but we should. static final DiagnosticType FUNCTION_LITERAL_UNDEFINED_THIS = DiagnosticType.warning( "JSC_FUNCTION_LITERAL_UNDEFINED_THIS", "Function literal argument refers to undefined this argument"); private final AbstractCompiler compiler; private final JSTypeRegistry registry; private final ReverseAbstractInterpreter reverseInterpreter; private final Scope syntacticScope; private final FlowScope functionScope; private final FlowScope bottomScope; private final Map<String, AssertionFunctionSpec> assertionFunctionsMap; // For convenience private final ObjectType unknownType; TypeInference(AbstractCompiler compiler, ControlFlowGraph<Node> cfg, ReverseAbstractInterpreter reverseInterpreter, Scope functionScope, Map<String, AssertionFunctionSpec> assertionFunctionsMap) { super(cfg, new LinkedFlowScope.FlowScopeJoinOp()); this.compiler = compiler; this.registry = compiler.getTypeRegistry(); this.reverseInterpreter = reverseInterpreter; this.unknownType = registry.getNativeObjectType(UNKNOWN_TYPE); this.syntacticScope = functionScope; inferArguments(functionScope); this.functionScope = LinkedFlowScope.createEntryLattice(functionScope); this.assertionFunctionsMap = assertionFunctionsMap; // For each local variable declared with the VAR keyword, the entry // type is VOID. Iterator<Var> varIt = functionScope.getDeclarativelyUnboundVarsWithoutTypes(); while (varIt.hasNext()) { Var var = varIt.next(); if (isUnflowable(var)) { continue; } this.functionScope.inferSlotType( var.getName(), getNativeType(VOID_TYPE)); } this.bottomScope = LinkedFlowScope.createEntryLattice( Scope.createLatticeBottom(functionScope.getRootNode())); } /** * Infers all of a function's arguments if their types aren't declared. */ private void inferArguments(Scope functionScope) { Node functionNode = functionScope.getRootNode(); Node astParameters = functionNode.getFirstChild().getNext(); Node iifeArgumentNode = null; if (NodeUtil.isCallOrNewTarget(functionNode)) { iifeArgumentNode = functionNode.getNext(); } FunctionType functionType = JSType.toMaybeFunctionType(functionNode.getJSType()); if (functionType != null) { Node parameterTypes = functionType.getParametersNode(); if (parameterTypes != null) { Node parameterTypeNode = parameterTypes.getFirstChild(); for (

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Node astParameter : astParameters.children()) { Var var = functionScope.getVar(astParameter.getString()); Preconditions.checkNotNull(var); if (var.isTypeInferred() && var.getType() == unknownType) { JSType newType = null; if (iifeArgumentNode != null) { newType = iifeArgumentNode.getJSType(); } else if (parameterTypeNode != null) { newType = parameterTypeNode.getJSType(); } if (newType != null) { var.setType(newType); astParameter.setJSType(newType); } } if (parameterTypeNode != null) { parameterTypeNode = parameterTypeNode.getNext(); } if (iifeArgumentNode != null) { iifeArgumentNode = iifeArgumentNode.getNext(); } } } } } @Override FlowScope createInitialEstimateLattice() { return bottomScope; } @Override FlowScope createEntryLattice() { return functionScope; } @Override FlowScope flowThrough(Node n, FlowScope input) { // If we have not walked a path from <entry> to <n>, then we don't // want to infer anything about this scope. if (input == bottomScope) { return input; } FlowScope output = input.createChildFlowScope(); output = traverse(n, output); return output; } @Override @SuppressWarnings({"fallthrough", "incomplete-switch"}) List<FlowScope> branchedFlowThrough(Node source, FlowScope input) { // NOTE(nicksantos): Right now, we just treat ON_EX edges like UNCOND // edges. If we wanted to be perfect, we'd actually JOIN all the out // lattices of this flow with the in lattice, and then make that the out // lattice for the ON_EX edge. But it's probably too expensive to be // worthwhile. FlowScope output = flowThrough(source, input); Node condition = null; FlowScope conditionFlowScope = null; BooleanOutcomePair conditionOutcomes = null; List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(source); List<FlowScope> result = Lists.newArrayListWithCapacity(branchEdges.size()); for (DiGraphEdge<Node, Branch> branchEdge : branchEdges) { Branch branch = branchEdge.getValue(); FlowScope newScope = output; switch (branch) { case ON_TRUE: if (NodeUtil.isForIn(source)) { // item is assigned a property name, so its type should be string. Node item = source.getFirstChild(); Node obj = item.getNext(); FlowScope informed = traverse(obj, output.createChildFlowScope()); if (item.isVar()) { item = item.getFirstChild(); } if (item.isName()) { JSType iterKeyType = getNativeType(STRING_TYPE); ObjectType objType = getJSType(obj).dereference(); JSType objIndexType = objType == null ? null : objType.getTemplateTypeMap().getTemplateType( registry.getObjectIndex

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Key()); if (objIndexType != null && !objIndexType.isUnknownType()) { JSType narrowedKeyType = iterKeyType.getGreatestSubtype(objIndexType); if (!narrowedKeyType.isEmptyType()) { iterKeyType = narrowedKeyType; } } redeclareSimpleVar(informed, item, iterKeyType); } newScope = informed; break; } // FALL THROUGH case ON_FALSE: if (condition == null) { condition = NodeUtil.getConditionExpression(source); if (condition == null && source.isCase()) { condition = source; // conditionFlowScope is cached from previous iterations // of the loop. if (conditionFlowScope == null) { conditionFlowScope = traverse( condition.getFirstChild(), output.createChildFlowScope()); } } } if (condition != null) { if (condition.isAnd() || condition.isOr()) { // When handling the short-circuiting binary operators, // the outcome scope on true can be different than the outcome // scope on false. // // TODO(nicksantos): The "right" way to do this is to // carry the known outcome all the way through the // recursive traversal, so that we can construct a // different flow scope based on the outcome. However, // this would require a bunch of code and a bunch of // extra computation for an edge case. This seems to be // a "good enough" approximation. // conditionOutcomes is cached from previous iterations // of the loop. if (conditionOutcomes == null) { conditionOutcomes = condition.isAnd() ? traverseAnd(condition, output.createChildFlowScope()) : traverseOr(condition, output.createChildFlowScope()); } newScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome( condition, conditionOutcomes.getOutcomeFlowScope( condition.getType(), branch == Branch.ON_TRUE), branch == Branch.ON_TRUE); } else { // conditionFlowScope is cached from previous iterations // of the loop. if (conditionFlowScope == null) { conditionFlowScope = traverse(condition, output.createChildFlowScope()); } newScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome( condition, conditionFlowScope, branch == Branch.ON_TRUE); } } break; } result.add(newScope.optimize()); } return result; } private FlowScope traverse(Node n, FlowScope scope) { switch (n.getType()) { case Token.ASSIGN: scope = traverseAssign(n, scope); break; case Token.NAME: scope = traverseName(n, scope); break; case Token.GETPROP: scope = traverseGetProp(n, scope); break; case Token.AND: scope = traverseAnd(n, scope).getJoinedFlowScope() .createChildFlowScope(); break; case Token.OR: scope = traverseOr(n, scope).getJoinedFlowScope() .createChildFlowScope(); break; case Token.HOOK: scope = traverseHook(n

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>, scope); break; case Token.OBJECTLIT: scope = traverseObjectLiteral(n, scope); break; case Token.CALL: scope = traverseCall(n, scope); break; case Token.NEW: scope = traverseNew(n, scope); break; case Token.ASSIGN_ADD: case Token.ADD: scope = traverseAdd(n, scope); break; case Token.POS: case Token.NEG: scope = traverse(n.getFirstChild(), scope); // Find types. n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.ARRAYLIT: scope = traverseArrayLiteral(n, scope); break; case Token.THIS: n.setJSType(scope.getTypeOfThis()); break; case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.LSH: case Token.RSH: case Token.ASSIGN_URSH: case Token.URSH: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITAND: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITOR: case Token.ASSIGN_MUL: case Token.ASSIGN_SUB: case Token.DIV: case Token.MOD: case Token.BITAND: case Token.BITXOR: case Token.BITOR: case Token.MUL: case Token.SUB: case Token.DEC: case Token.INC: case Token.BITNOT: scope = traverseChildren(n, scope); n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.PARAM_LIST: scope = traverse(n.getFirstChild(), scope); n.setJSType(getJSType(n.getFirstChild())); break; case Token.COMMA: scope = traverseChildren(n, scope); n.setJSType(getJSType(n.getLastChild())); break; case Token.TYPEOF: scope = traverseChildren(n, scope); n.setJSType(getNativeType(STRING_TYPE)); break; case Token.DELPROP: case Token.LT: case Token.LE: case Token.GT: case Token.GE: case Token.NOT: case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.INSTANCEOF: case Token.IN: scope = traverseChildren(n, scope); n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.GETELEM: scope = traverseGetElem(n, scope); break; case Token.EXPR_RESULT: scope = traverseChildren(n, scope); if (n.getFirstChild().isGetProp()) { ensurePropertyDeclared(n.getFirstChild()); } break; case Token.SWITCH: scope = traverse(n.getFirstChild(), scope); break; case Token.RETURN: scope = traverseReturn(n, scope); break; case Token.VAR: case Token.THROW: scope = traverseChildren(n, scope);

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> break; case Token.CATCH: scope = traverseCatch(n, scope); break; case Token.CAST: scope = traverseChildren(n, scope); JSDocInfo info = n.getJSDocInfo(); if (info != null && info.hasType()) { n.setJSType(info.getType().evaluate(syntacticScope, registry)); } break; } return scope; } /** * Traverse a return value. */ private FlowScope traverseReturn(Node n, FlowScope scope) { scope = traverseChildren(n, scope); Node retValue = n.getFirstChild(); if (retValue != null) { JSType type = functionScope.getRootNode().getJSType(); if (type != null) { FunctionType fnType = type.toMaybeFunctionType(); if (fnType != null) { inferPropertyTypesToMatchConstraint( retValue.getJSType(), fnType.getReturnType()); } } } return scope; } /** * Any value can be thrown, so it's really impossible to determine the type * of a CATCH param. Treat it as the UNKNOWN type. */ private FlowScope traverseCatch(Node catchNode, FlowScope scope) { Node name = catchNode.getFirstChild(); JSType type; // If the catch expression name was declared in the catch use that type, // otherwise use "unknown". JSDocInfo info = name.getJSDocInfo(); if (info != null && info.hasType()) { type = info.getType().evaluate(syntacticScope, registry); } else { type = getNativeType(JSTypeNative.UNKNOWN_TYPE); } redeclareSimpleVar(scope, name, type); name.setJSType(type); return scope; } private FlowScope traverseAssign(Node n, FlowScope scope) { Node left = n.getFirstChild(); Node right = n.getLastChild(); scope = traverseChildren(n, scope); JSType leftType = left.getJSType(); JSType rightType = getJSType(right); n.setJSType(rightType); updateScopeForTypeChange(scope, left, leftType, rightType); return scope; } /** * Updates the scope according to the result of a type change, like * an assignment or a type cast. */ private void updateScopeForTypeChange( FlowScope scope, Node left, JSType leftType, JSType resultType) { Preconditions.checkNotNull(resultType); switch (left.getType()) { case Token.NAME: String varName = left.getString(); Var var = syntacticScope.getVar(varName); // When looking at VAR initializers for declared VARs, we trust // the declared type over the type it's being initialized to. // This has two purposes: // 1) We avoid re-declaring declared variables so that built-in // types defined in externs are not redeclared. // 2) When there's a lexical closure like // /** @type {?string} */ var x = null; // function f() { x = 'xyz'; } // the inference

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> will ignore the lexical closure, // which is just wrong. This bug needs to be fixed eventually. boolean isVarDeclaration = left.hasChildren(); if (!isVarDeclaration || var == null || var.isTypeInferred()) { redeclareSimpleVar(scope, left, resultType); } left.setJSType(isVarDeclaration || leftType == null ? resultType : null); if (var != null && var.isTypeInferred()) { JSType oldType = var.getType(); var.setType(oldType == null ? resultType : oldType.getLeastSupertype(resultType)); } break; case Token.GETPROP: String qualifiedName = left.getQualifiedName(); if (qualifiedName != null) { scope.inferQualifiedSlot(left, qualifiedName, leftType == null ? unknownType : leftType, resultType); } left.setJSType(resultType); ensurePropertyDefined(left, resultType); break; } } /** * Defines a property if the property has not been defined yet. */ private void ensurePropertyDefined(Node getprop, JSType rightType) { String propName = getprop.getLastChild().getString(); Node obj = getprop.getFirstChild(); JSType nodeType = getJSType(obj); ObjectType objectType = ObjectType.cast( nodeType.restrictByNotNullOrUndefined()); if (objectType == null) { registry.registerPropertyOnType(propName, nodeType); } else { // Don't add the property to @struct objects outside a constructor if (nodeType.isStruct() && !objectType.hasProperty(propName)) { if (!(obj.isThis() && getJSType(syntacticScope.getRootNode()).isConstructor())) { return; } } if (ensurePropertyDeclaredHelper(getprop, objectType)) { return; } if (!objectType.isPropertyTypeDeclared(propName)) { // We do not want a "stray" assign to define an inferred property // for every object of this type in the program. So we use a heuristic // approach to determine whether to infer the property. // // 1) If the property is already defined, join it with the previously // inferred type. // 2) If this isn't an instance object, define it. // 3) If the property of an object is being assigned in the constructor, // define it. // 4) If this is a stub, define it. // 5) Otherwise, do not define the type, but declare it in the registry // so that we can use it for missing property checks. if (objectType.hasProperty(propName) || !objectType.isInstanceType()) { if ("prototype".equals(propName)) { objectType.defineDeclaredProperty(propName, rightType, getprop); } else { objectType.defineInferredProperty(propName, rightType, getprop); } } else if (obj.isThis() && getJSType(syntacticScope.getRootNode()).isConstructor()) { objectType.defineInferredProperty(propName, rightType, getprop); }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> else { registry.registerPropertyOnType(propName, objectType); } } } } /** * Defines a declared property if it has not been defined yet. * * This handles the case where a property is declared on an object where * the object type is inferred, and so the object type will not * be known in {@code TypedScopeCreator}. */ private void ensurePropertyDeclared(Node getprop) { ObjectType ownerType = ObjectType.cast( getJSType(getprop.getFirstChild()).restrictByNotNullOrUndefined()); if (ownerType != null) { ensurePropertyDeclaredHelper(getprop, ownerType); } } /** * Declares a property on its owner, if necessary. * @return True if a property was declared. */ private boolean ensurePropertyDeclaredHelper( Node getprop, ObjectType objectType) { String propName = getprop.getLastChild().getString(); String qName = getprop.getQualifiedName(); if (qName != null) { Var var = syntacticScope.getVar(qName); if (var != null && !var.isTypeInferred()) { // Handle normal declarations that could not be addressed earlier. if (propName.equals("prototype") || // Handle prototype declarations that could not be addressed earlier. (!objectType.hasOwnProperty(propName) && (!objectType.isInstanceType() || (var.isExtern() && !objectType.isNativeObjectType())))) { return objectType.defineDeclaredProperty( propName, var.getType(), getprop); } } } return false; } private FlowScope traverseName(Node n, FlowScope scope) { String varName = n.getString(); Node value = n.getFirstChild(); JSType type = n.getJSType(); if (value != null) { scope = traverse(value, scope); updateScopeForTypeChange(scope, n, n.getJSType() /* could be null */, getJSType(value)); return scope; } else { StaticSlot<JSType> var = scope.getSlot(varName); if (var != null) { // There are two situations where we don't want to use type information // from the scope, even if we have it. // 1) The var is escaped and assigned in an inner scope, e.g., // function f() { var x = 3; function g() { x = null } (x); } boolean isInferred = var.isTypeInferred(); boolean unflowable = isInferred && isUnflowable(syntacticScope.getVar(varName)); // 2) We're reading type information from another scope for an // inferred variable. That variable is assigned more than once, // and we can't know which type we're getting. // // var t = null; function f() { (t); } doStuff(); t = {}; // // Notice that this heuristic isn't perfect. For example, you might // have: // // function f() { (t); } f(); var t = 3; // // In this case, we would infer the first reference to t as // type {number}, even though it

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>'s undefined. boolean nonLocalInferredSlot = false; if (isInferred && syntacticScope.isLocal()) { Var maybeOuterVar = syntacticScope.getParent().getVar(varName); if (var == maybeOuterVar && !maybeOuterVar.isMarkedAssignedExactlyOnce()) { nonLocalInferredSlot = true; } } if (!unflowable && !nonLocalInferredSlot) { type = var.getType(); if (type == null) { type = unknownType; } } } } n.setJSType(type); return scope; } /** Traverse each element of the array. */ private FlowScope traverseArrayLiteral(Node n, FlowScope scope) { scope = traverseChildren(n, scope); n.setJSType(getNativeType(ARRAY_TYPE)); return scope; } private FlowScope traverseObjectLiteral(Node n, FlowScope scope) { JSType type = n.getJSType(); Preconditions.checkNotNull(type); for (Node name = n.getFirstChild(); name != null; name = name.getNext()) { scope = traverse(name.getFirstChild(), scope); } // Object literals can be reflected on other types. // See CodingConvention#getObjectLiteralCase and goog.object.reflect. // Ignore these types of literals. ObjectType objectType = ObjectType.cast(type); if (objectType == null || n.getBooleanProp(Node.REFLECTED_OBJECT) || objectType.isEnumType()) { return scope; } String qObjName = NodeUtil.getBestLValueName( NodeUtil.getBestLValue(n)); for (Node name = n.getFirstChild(); name != null; name = name.getNext()) { String memberName = NodeUtil.getObjectLitKeyName(name); if (memberName != null) { JSType rawValueType = name.getFirstChild().getJSType(); JSType valueType = NodeUtil.getObjectLitKeyTypeFromValueType( name, rawValueType); if (valueType == null) { valueType = unknownType; } objectType.defineInferredProperty(memberName, valueType, name); // Do normal flow inference if this is a direct property assignment. if (qObjName != null && name.isStringKey()) { String qKeyName = qObjName + "." + memberName; Var var = syntacticScope.getVar(qKeyName); JSType oldType = var == null ? null : var.getType(); if (var != null && var.isTypeInferred()) { var.setType(oldType == null ? valueType : oldType.getLeastSupertype(oldType)); } scope.inferQualifiedSlot(name, qKeyName, oldType == null ? unknownType : oldType, valueType); } } else { n.setJSType(unknownType); } } return scope; } private FlowScope traverseAdd(Node n, FlowScope scope) { Node left = n.getFirstChild(); Node right = left.getNext(); scope = traverseChildren(n, scope); JSType leftType = left.getJSType(); JSType rightType = right.get

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>JSType(); JSType type = unknownType; if (leftType != null && rightType != null) { boolean leftIsUnknown = leftType.isUnknownType(); boolean rightIsUnknown = rightType.isUnknownType(); if (leftIsUnknown && rightIsUnknown) { type = unknownType; } else if ((!leftIsUnknown && leftType.isString()) || (!rightIsUnknown && rightType.isString())) { type = getNativeType(STRING_TYPE); } else if (leftIsUnknown || rightIsUnknown) { type = unknownType; } else if (isAddedAsNumber(leftType) && isAddedAsNumber(rightType)) { type = getNativeType(NUMBER_TYPE); } else { type = registry.createUnionType(STRING_TYPE, NUMBER_TYPE); } } n.setJSType(type); if (n.isAssignAdd()) { updateScopeForTypeChange(scope, left, leftType, type); } return scope; } private boolean isAddedAsNumber(JSType type) { return type.isSubtype(registry.createUnionType(VOID_TYPE, NULL_TYPE, NUMBER_VALUE_OR_OBJECT_TYPE, BOOLEAN_TYPE, BOOLEAN_OBJECT_TYPE)); } private FlowScope traverseHook(Node n, FlowScope scope) { Node condition = n.getFirstChild(); Node trueNode = condition.getNext(); Node falseNode = n.getLastChild(); // verify the condition scope = traverse(condition, scope); // reverse abstract interpret the condition to produce two new scopes FlowScope trueScope = reverseInterpreter. getPreciserScopeKnowingConditionOutcome( condition, scope, true); FlowScope falseScope = reverseInterpreter. getPreciserScopeKnowingConditionOutcome( condition, scope, false); // traverse the true node with the trueScope traverse(trueNode, trueScope.createChildFlowScope()); // traverse the false node with the falseScope traverse(falseNode, falseScope.createChildFlowScope()); // meet true and false nodes' types and assign JSType trueType = trueNode.getJSType(); JSType falseType = falseNode.getJSType(); if (trueType != null && falseType != null) { n.setJSType(trueType.getLeastSupertype(falseType)); } else { n.setJSType(null); } return scope.createChildFlowScope(); } private FlowScope traverseCall(Node n, FlowScope scope) { scope = traverseChildren(n, scope); Node left = n.getFirstChild(); JSType functionType = getJSType(left).restrictByNotNullOrUndefined(); if (functionType.isFunctionType()) { FunctionType fnType = functionType.toMaybeFunctionType(); n.setJSType(fnType.getReturnType()); backwardsInferenceFromCallSite(n, fnType); } else if (functionType.isEquivalentTo( getNativeType(CHECKED_UNKNOWN_TYPE))) { n.setJSType(getNativeType(CHECKED_UNKNOWN_TYPE)); } scope = tightenTypesAfterAssertions(scope, n); return scope; } private FlowScope tightenTypesAfter

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Assertions(FlowScope scope, Node callNode) { Node left = callNode.getFirstChild(); Node firstParam = left.getNext(); AssertionFunctionSpec assertionFunctionSpec = assertionFunctionsMap.get(left.getQualifiedName()); if (assertionFunctionSpec == null || firstParam == null) { return scope; } Node assertedNode = assertionFunctionSpec.getAssertedParam(firstParam); if (assertedNode == null) { return scope; } JSType assertedType = assertionFunctionSpec.getAssertedType( callNode, registry); String assertedNodeName = assertedNode.getQualifiedName(); JSType narrowed; // Handle assertions that enforce expressions evaluate to true. if (assertedType == null) { // Handle arbitrary expressions within the assert. scope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome( assertedNode, scope, true); // Build the result of the assertExpression narrowed = getJSType(assertedNode).restrictByNotNullOrUndefined(); } else { // Handle assertions that enforce expressions are of a certain type. JSType type = getJSType(assertedNode); narrowed = type.getGreatestSubtype(assertedType); if (assertedNodeName != null && type.differsFrom(narrowed)) { scope = narrowScope(scope, assertedNode, narrowed); } } callNode.setJSType(narrowed); return scope; } private FlowScope narrowScope(FlowScope scope, Node node, JSType narrowed) { if (node.isThis()) { // "this" references don't need to be modeled in the control flow graph. return scope; } scope = scope.createChildFlowScope(); if (node.isGetProp()) { scope.inferQualifiedSlot( node, node.getQualifiedName(), getJSType(node), narrowed); } else { redeclareSimpleVar(scope, node, narrowed); } return scope; } /** * We only do forward type inference. We do not do full backwards * type inference. * * In other words, if we have, * <code> * var x = f(); * g(x); * </code> * a forward type-inference engine would try to figure out the type * of "x" from the return type of "f". A backwards type-inference engine * would try to figure out the type of "x" from the parameter type of "g". * * However, there are a few special syntactic forms where we do some * some half-assed backwards type-inference, because programmers * expect it in this day and age. To take an example from Java, * <code> * List<String> x = Lists.newArrayList(); * </code> * The Java compiler will be able to infer the generic type of the List * returned by newArrayList(). * * In much the same way, we do some special-case backwards inference for * JS. Those cases are enumerated here. */ private void backwardsInferenceFromCallSite(Node n, FunctionType fnType) { boolean updatedFnType = inferTemplatedTypesForCall(n,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>); if (!resolved.isUnknownType()) { if (previous == null) { map.put(template, resolved); } else { JSType join = previous.getLeastSupertype(resolved); map.put(template, join); } } } private static class TemplateTypeReplacer extends ModificationVisitor { private final Map<TemplateType, JSType> replacements; private final JSTypeRegistry registry; boolean madeChanges = false; TemplateTypeReplacer( JSTypeRegistry registry, Map<TemplateType, JSType> replacements) { super(registry); this.registry = registry; this.replacements = replacements; } @Override public JSType caseTemplateType(TemplateType type) { madeChanges = true; JSType replacement = replacements.get(type); return replacement != null ? replacement : registry.getNativeType(UNKNOWN_TYPE); } } /** * For functions with function(this: T, ...) and T as parameters, type * inference will set the type of this on a function literal argument to the * the actual type of T. */ private boolean inferTemplatedTypesForCall( Node n, FunctionType fnType) { if (fnType.getTemplateTypeMap().getTemplateKeys().isEmpty()) { return false; } // Try to infer the template types Map<TemplateType, JSType> inferred = inferTemplateTypesFromParameters( fnType, n); // Replace all template types. If we couldn't find a replacement, we // replace it with UNKNOWN. TemplateTypeReplacer replacer = new TemplateTypeReplacer( registry, inferred); Node callTarget = n.getFirstChild(); FunctionType replacementFnType = fnType.visit(replacer) .toMaybeFunctionType(); Preconditions.checkNotNull(replacementFnType); callTarget.setJSType(replacementFnType); n.setJSType(replacementFnType.getReturnType()); return replacer.madeChanges; } private FlowScope traverseNew(Node n, FlowScope scope) { scope = traverseChildren(n, scope); Node constructor = n.getFirstChild(); JSType constructorType = constructor.getJSType(); JSType type = null; if (constructorType != null) { constructorType = constructorType.restrictByNotNullOrUndefined(); if (constructorType.isUnknownType()) { type = unknownType; } else { FunctionType ct = constructorType.toMaybeFunctionType(); if (ct == null && constructorType instanceof FunctionType) { // If constructorType is a NoObjectType, then toMaybeFunctionType will // return null. But NoObjectType implements the FunctionType // interface, precisely because it can validly construct objects. ct = (FunctionType) constructorType; } if (ct != null && ct.isConstructor()) { backwardsInferenceFromCallSite(n, ct); // If necessary, create a TemplatizedType wrapper around the instance // type, based on the types of the constructor parameters. ObjectType instanceType = ct.getInstanceType(); Map<TemplateType, JSType> inferredTypes = inferTemplateTypesFromParameters(ct, n); if (inferredTypes.isEmpty()) { type = instanceType; } else {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> type = registry.createTemplatizedType(instanceType, inferredTypes); } } } } n.setJSType(type); return scope; } private BooleanOutcomePair traverseAnd(Node n, FlowScope scope) { return traverseShortCircuitingBinOp(n, scope, true); } private FlowScope traverseChildren(Node n, FlowScope scope) { for (Node el = n.getFirstChild(); el != null; el = el.getNext()) { scope = traverse(el, scope); } return scope; } private FlowScope traverseGetElem(Node n, FlowScope scope) { scope = traverseChildren(n, scope); JSType type = getJSType(n.getFirstChild()).restrictByNotNullOrUndefined(); TemplateTypeMap typeMap = type.getTemplateTypeMap(); if (typeMap.hasTemplateType(registry.getObjectElementKey())) { n.setJSType(typeMap.getTemplateType(registry.getObjectElementKey())); } return dereferencePointer(n.getFirstChild(), scope); } private FlowScope traverseGetProp(Node n, FlowScope scope) { Node objNode = n.getFirstChild(); Node property = n.getLastChild(); scope = traverseChildren(n, scope); n.setJSType( getPropertyType( objNode.getJSType(), property.getString(), n, scope)); return dereferencePointer(n.getFirstChild(), scope); } /** * Suppose X is an object with inferred properties. * Suppose also that X is used in a way where it would only type-check * correctly if some of those properties are widened. * Then we should be polite and automatically widen X's properties for him. * * For a concrete example, consider: * param x {{prop: (number|undefined)}} * function f(x) {} * f({}); * * If we give the anonymous object an inferred property of (number|undefined), * then this code will type-check appropriately. */ private static void inferPropertyTypesToMatchConstraint( JSType type, JSType constraint) { if (type == null || constraint == null) { return; } type.matchConstraint(constraint); } /** * If we access a property of a symbol, then that symbol is not * null or undefined. */ private FlowScope dereferencePointer(Node n, FlowScope scope) { if (n.isQualifiedName()) { JSType type = getJSType(n); JSType narrowed = type.restrictByNotNullOrUndefined(); if (type != narrowed) { scope = narrowScope(scope, n, narrowed); } } return scope; } private JSType getPropertyType(JSType objType, String propName, Node n, FlowScope scope) { // We often have a couple of different types to choose from for the // property. Ordered by accuracy, we have // 1) A locally inferred qualified name (which is in the FlowScope) // 2) A globally declared qualified name (which is in the FlowScope) // 3) A property on the owner type (which is on objType) // 4) A

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> name in the type registry (as a last resort) JSType propertyType = null; boolean isLocallyInferred = false; // Scopes sometimes contain inferred type info about qualified names. String qualifiedName = n.getQualifiedName(); StaticSlot<JSType> var = scope.getSlot(qualifiedName); if (var != null) { JSType varType = var.getType(); if (varType != null) { boolean isDeclared = !var.isTypeInferred(); isLocallyInferred = (var != syntacticScope.getSlot(qualifiedName)); if (isDeclared || isLocallyInferred) { propertyType = varType; } } } if (propertyType == null && objType != null) { JSType foundType = objType.findPropertyType(propName); if (foundType != null) { propertyType = foundType; } } if (propertyType != null && objType != null) { JSType restrictedObjType = objType.restrictByNotNullOrUndefined(); if (restrictedObjType.isTemplatizedType() && propertyType.hasAnyTemplateTypes()) { TemplateTypeMap typeMap = restrictedObjType.getTemplateTypeMap(); TemplateTypeMapReplacer replacer = new TemplateTypeMapReplacer( registry, typeMap); propertyType = propertyType.visit(replacer); } } if ((propertyType == null || propertyType.isUnknownType()) && qualifiedName != null) { // If we find this node in the registry, then we can infer its type. ObjectType regType = ObjectType.cast(registry.getType(qualifiedName)); if (regType != null) { propertyType = regType.getConstructor(); } } if (propertyType == null) { return unknownType; } else if (propertyType.isEquivalentTo(unknownType) && isLocallyInferred) { // If the type has been checked in this scope, // then use CHECKED_UNKNOWN_TYPE instead to indicate that. return getNativeType(CHECKED_UNKNOWN_TYPE); } else { return propertyType; } } private BooleanOutcomePair traverseOr(Node n, FlowScope scope) { return traverseShortCircuitingBinOp(n, scope, false); } private BooleanOutcomePair traverseShortCircuitingBinOp( Node n, FlowScope scope, boolean condition) { Node left = n.getFirstChild(); Node right = n.getLastChild(); // type the left node BooleanOutcomePair leftLiterals = traverseWithinShortCircuitingBinOp(left, scope.createChildFlowScope()); JSType leftType = left.getJSType(); // reverse abstract interpret the left node to produce the correct // scope in which to verify the right node FlowScope rightScope = reverseInterpreter. getPreciserScopeKnowingConditionOutcome( left, leftLiterals.getOutcomeFlowScope(left.getType(), condition), condition); // type the right node BooleanOutcomePair rightLiterals = traverseWithinShortCircuitingBinOp( right, rightScope.createChildFlowScope()); JSType rightType = right.getJSType(); JSType type; BooleanOutcomePair literals; if (leftType != null

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> && rightType != null) { leftType = leftType.getRestrictedTypeGivenToBooleanOutcome(!condition); if (leftLiterals.toBooleanOutcomes == BooleanLiteralSet.get(!condition)) { // Use the restricted left type, since the right side never gets // evaluated. type = leftType; literals = leftLiterals; } else { // Use the join of the restricted left type knowing the outcome of the // ToBoolean predicate and of the right type. type = leftType.getLeastSupertype(rightType); literals = getBooleanOutcomePair(leftLiterals, rightLiterals, condition); } // Exclude the boolean type if the literal set is empty because a boolean // can never actually be returned. if (literals.booleanValues == BooleanLiteralSet.EMPTY && getNativeType(BOOLEAN_TYPE).isSubtype(type)) { // Exclusion only make sense for a union type. if (type.isUnionType()) { type = type.toMaybeUnionType().getRestrictedUnion( getNativeType(BOOLEAN_TYPE)); } } } else { type = null; literals = new BooleanOutcomePair( BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, leftLiterals.getJoinedFlowScope(), rightLiterals.getJoinedFlowScope()); } n.setJSType(type); return literals; } private BooleanOutcomePair traverseWithinShortCircuitingBinOp(Node n, FlowScope scope) { switch (n.getType()) { case Token.AND: return traverseAnd(n, scope); case Token.OR: return traverseOr(n, scope); default: scope = traverse(n, scope); return newBooleanOutcomePair(n.getJSType(), scope); } } /** * Infers the boolean outcome pair that can be taken by a * short-circuiting binary operation ({@code &&} or {@code ||}). * @see #getBooleanOutcomes(BooleanLiteralSet, BooleanLiteralSet, boolean) */ BooleanOutcomePair getBooleanOutcomePair(BooleanOutcomePair left, BooleanOutcomePair right, boolean condition) { return new BooleanOutcomePair( getBooleanOutcomes(left.toBooleanOutcomes, right.toBooleanOutcomes, condition), getBooleanOutcomes(left.booleanValues, right.booleanValues, condition), left.getJoinedFlowScope(), right.getJoinedFlowScope()); } /** * Infers the boolean literal set that can be taken by a * short-circuiting binary operation ({@code &&} or {@code ||}). * @param left the set of possible {@code ToBoolean} predicate results for * the expression on the left side of the operator * @param right the set of possible {@code ToBoolean} predicate results for * the expression on the right side of the operator * @param condition the left side {@code ToBoolean} predicate result that * causes the right side to get evaluated (i.e. not short-circuited) * @return a set of possible {@code ToBoolean} predicate results for the * entire expression */ static BooleanLiteralSet getBooleanOutcomes(BooleanLiteralSet left, BooleanLiteralSet right, boolean condition) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> return right.union(left.intersection(BooleanLiteralSet.get(!condition))); } /** * When traversing short-circuiting binary operations, we need to keep track * of two sets of boolean literals: * 1. {@code toBooleanOutcomes}: boolean literals as converted from any types, * 2. {@code booleanValues}: boolean literals from just boolean types. */ private final class BooleanOutcomePair { final BooleanLiteralSet toBooleanOutcomes; final BooleanLiteralSet booleanValues; // The scope if only half of the expression executed, when applicable. final FlowScope leftScope; // The scope when the whole expression executed. final FlowScope rightScope; // The scope when we don't know how much of the expression is executed. FlowScope joinedScope = null; BooleanOutcomePair( BooleanLiteralSet toBooleanOutcomes, BooleanLiteralSet booleanValues, FlowScope leftScope, FlowScope rightScope) { this.toBooleanOutcomes = toBooleanOutcomes; this.booleanValues = booleanValues; this.leftScope = leftScope; this.rightScope = rightScope; } /** * Gets the safe estimated scope without knowing if all of the * subexpressions will be evaluated. */ FlowScope getJoinedFlowScope() { if (joinedScope == null) { if (leftScope == rightScope) { joinedScope = rightScope; } else { joinedScope = join(leftScope, rightScope); } } return joinedScope; } /** * Gets the outcome scope if we do know the outcome of the entire * expression. */ FlowScope getOutcomeFlowScope(int nodeType, boolean outcome) { if (nodeType == Token.AND && outcome || nodeType == Token.OR && !outcome) { // We know that the whole expression must have executed. return rightScope; } else { return getJoinedFlowScope(); } } } private BooleanOutcomePair newBooleanOutcomePair( JSType jsType, FlowScope flowScope) { if (jsType == null) { return new BooleanOutcomePair( BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, flowScope, flowScope); } return new BooleanOutcomePair(jsType.getPossibleToBooleanOutcomes(), registry.getNativeType(BOOLEAN_TYPE).isSubtype(jsType) ? BooleanLiteralSet.BOTH : BooleanLiteralSet.EMPTY, flowScope, flowScope); } private void redeclareSimpleVar( FlowScope scope, Node nameNode, JSType varType) { Preconditions.checkState(nameNode.isName()); String varName = nameNode.getString(); if (varType == null) { varType = getNativeType(JSTypeNative.UNKNOWN_TYPE); } if (isUnflowable(syntacticScope.getVar(varName))) { return; } scope.inferSlotType(varName, varType); } private boolean isUnflowable(Var v) { return v != null && v.isLocal() && v.isMarkedEscaped() && // It's OK to flow a variable in the scope where it's escaped. v.getScope() == syntacticScope; } /**

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS><Integer> nodeTypes; private final boolean include; /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) { this(nodeTypes, true); } /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include/exclude in the traversal * @param include whether to include or exclude the nodes in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes, boolean include) { this.nodeTypes = nodeTypes; this.include = include; } @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return include == nodeTypes.contains(n.getType()); } } /** * Creates a node traversal using the specified callback interface. */ public NodeTraversal(AbstractCompiler compiler, Callback cb) { this(compiler, cb, new SyntacticScopeCreator(compiler)); } /** * Creates a node traversal using the specified callback interface * and the scope creator. */ public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) { this.callback = cb; if (cb instanceof ScopedCallback) { this.scopeCallback = (ScopedCallback) cb; } this.compiler = compiler; this.inputId = null; this.sourceName = ""; this.scopeCreator = scopeCreator; } private void throwUnexpectedException(Exception unexpectedException) { // If there's an unexpected exception, try to get the // line number of the code that caused it. String message = unexpectedException.getMessage(); // TODO(user): It is possible to get more information if curNode or // its parent is missing. We still have the scope stack in which it is still // very useful to find out at least which function caused the exception. if (inputId != null) { message = unexpectedException.getMessage() + "\n" + formatNodeContext("Node", curNode) + (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent())); } compiler.throwInternalError(message, unexpectedException); } private String formatNodeContext(String label, Node n) { if (n == null) { return " " + label + ": NULL"; } return " " + label + "(" + n.toString(false, false, false) + "): " + formatNodePosition(n); } /** * Traverses a parse tree recursively. */ public void traverse(Node root) { try { inputId = NodeUtil.getInputId(root); sourceName = ""; curNode = root; pushScope(root); // null parent ensures that the shallow callbacks will traverse root traverseBranch(root, null); popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } public void traverseRoots(Node ... roots) { traverseRoots(Lists.newArrayList(roots)); } public void traverseRoots(List<Node> roots) { if (roots.isEmpty()) { return; }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(compiler, cb); t.traverseRoots(roots); } public static void traverseRoots( AbstractCompiler compiler, Callback cb, Node ... roots) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } /** * Traverses a branch. */ private void traverseBranch(Node n, Node parent) { int type = n.getType(); if (type == Token.SCRIPT) { inputId = n.getInputId(); sourceName = getSourceName(n); } curNode = n; if (!callback.shouldTraverse(this, n, parent)) { return; } if (type == Token.FUNCTION) { traverseFunction(n, parent); } else { for (Node child = n.getFirstChild(); child != null; ) { // child could be replaced, in which case our child node // would no longer point to the true next Node next = child.getNext(); traverseBranch(child, n); child = next; } } curNode = n; callback.visit(this, n, parent); } /** Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.isFunction()); final Node fnName = n.getFirstChild(); boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n); if (!isFunctionExpression) { // Functions declarations are in the scope containing the declaration. traverseBranch(fnName, n); } curNode = n; pushScope(n); if (isFunctionExpression) { // Function expression names are only accessible within the function // scope. traverseBranch(fnName, n); } final Node args = fnName.getNext(); final Node body = args.getNext(); // Args traverseBranch(args, n); // Body Preconditions.checkState(body.getNext() == null && body.isBlock(), body); traverseBranch(body, n); popScope(); } /** Examines the functions stack for the last instance of a function node. */ @SuppressWarnings("unchecked") public Node getEnclosingFunction() { if (scopes.size() + scopeRoots.size() < 2) { return null; } else { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Node node) { Preconditions.checkState(curNode != null); compiler.setScope(node); scopeRoots.push(node); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Scope s) { Preconditions.checkState(curNode != null); compiler.setScope(s.getRootNode()); scopes.push(s); cfgs.push(null); if (scopeCallback !=

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> null) { scopeCallback.enterScope(this); } } /** Pops back to the previous scope (e.g. when leaving a function). */ private void popScope() { if (scopeCallback != null) { scopeCallback.exitScope(this); } if (scopeRoots.isEmpty()) { scopes.pop(); } else { scopeRoots.pop(); } cfgs.pop(); if (hasScope()) { compiler.setScope(getScopeRoot()); } } /** Gets the current scope. */ public Scope getScope() { Scope scope = scopes.isEmpty() ? null : scopes.peek(); if (scopeRoots.isEmpty()) { return scope; } Iterator<Node> it = scopeRoots.descendingIterator(); while (it.hasNext()) { scope = scopeCreator.createScope(it.next(), scope); scopes.push(scope); } scopeRoots.clear(); // No need to call compiler.setScope; the top scopeRoot is now the top scope return scope; } /** Gets the control flow graph for the current JS scope. */ public ControlFlowGraph<Node> getControlFlowGraph() { if (cfgs.peek() == null) { ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true); cfa.process(null, getScopeRoot()); cfgs.pop(); cfgs.push(cfa.getCfg()); } return cfgs.peek(); } /** Returns the current scope's root. */ public Node getScopeRoot() { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } /** * Determines whether the traversal is currently in the global scope. */ boolean inGlobalScope() { return getScopeDepth() <= 1; } int getScopeDepth() { return scopes.size() + scopeRoots.size(); } public boolean hasScope() { return !(scopes.isEmpty() && scopeRoots.isEmpty()); } /** Reports a diagnostic (error or warning) */ public void report(Node n, DiagnosticType diagnosticType, String... arguments) { JSError error = JSError.make( getBestSourceFileName(n), n, diagnosticType, arguments); compiler.report(error); } private static String getSourceName(Node n) { String name = n.getSourceFileName(); return name == null ? "" : name; } InputId getInputId() { return inputId; } /** * Creates a JSError during NodeTraversal. * * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public JSError makeError(Node n, CheckLevel level, DiagnosticType type, String... arguments) { return JSError.make(getBestSourceFileName(n), n, level, type, arguments); } /** * Creates a JSError during NodeTraversal. * * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> new Node(Token.FALSE); } public static Node nullNode() { return new Node(Token.NULL); } // helper methods private static Node binaryOp(int token, Node expr1, Node expr2) { Preconditions.checkState(mayBeExpression(expr1)); Preconditions.checkState(mayBeExpression(expr2)); return new Node(token, expr1, expr2); } private static Node unaryOp(int token, Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(token, expr); } private static boolean mayBeExpressionOrEmpty(Node n) { return n.isEmpty() || mayBeExpression(n); } private static boolean isAssignmentTarget(Node n) { return n.isName() || n.isGetProp() || n.isGetElem(); } // NOTE: some nodes are neither statements nor expression nodes: // SCRIPT, LABEL_NAME, PARAM_LIST, CASE, DEFAULT_CASE, CATCH // GETTER_DEF, SETTER_DEF /** * It isn't possible to always determine if a detached node is a expression, * so make a best guess. */ private static boolean mayBeStatementNoReturn(Node n) { switch (n.getType()) { case Token.EMPTY: case Token.FUNCTION: // EMPTY and FUNCTION are used both in expression and statement // contexts return true; case Token.BLOCK: case Token.BREAK: case Token.CONST: case Token.CONTINUE: case Token.DEBUGGER: case Token.DO: case Token.EXPR_RESULT: case Token.FOR: case Token.IF: case Token.LABEL: case Token.SWITCH: case Token.THROW: case Token.TRY: case Token.VAR: case Token.WHILE: case Token.WITH: return true; default: return false; } } /** * It isn't possible to always determine if a detached node is a expression, * so make a best guess. */ private static boolean mayBeStatement(Node n) { if (!mayBeStatementNoReturn(n)) { return n.isReturn(); } return true; } /** * It isn't possible to always determine if a detached node is a expression, * so make a best guess. */ private static boolean mayBeExpression(Node n) { switch (n.getType()) { case Token.FUNCTION: // FUNCTION is used both in expression and statement // contexts. return true; case Token.ADD: case Token.AND: case Token.ARRAYLIT: case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.BITAND: case Token.BITOR: case Token.BITNOT: case Token.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> */ public boolean isOptionalArg() { return root.getType() == Token.EQUALS; } /** * @return Whether this expression denotes a rest args {@code @param}. */ public boolean isVarArgs() { return root.getType() == Token.ELLIPSIS; } /** * Evaluates the type expression into a {@code JSType} object. */ public JSType evaluate(StaticScope<JSType> scope, JSTypeRegistry registry) { JSType type = registry.createFromTypeNodes(root, sourceName, scope); root.setJSType(type); return type; } @Override public boolean equals(Object other) { return other instanceof JSTypeExpression && ((JSTypeExpression) other).root.isEquivalentTo(root); } @Override public int hashCode() { return root.toStringTree().hashCode(); } /** * @return The source for this type expression. Note that it will not * contain an expression if there's an @override tag. */ public Node getRoot() { return root; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Map() { this(Maps.<String, Property>newTreeMap()); } private PropertyMap(Map<String, Property> underlyingMap) { this.properties = underlyingMap; } static PropertyMap immutableEmptyMap() { return EMPTY_MAP; } void setParentSource(ObjectType ownerType) { if (this != EMPTY_MAP) { this.parentSource = ownerType; } } /** Returns the direct parent of this property map. */ PropertyMap getPrimaryParent() { if (parentSource == null) { return null; } ObjectType iProto = parentSource.getImplicitPrototype(); return iProto == null ? null : iProto.getPropertyMap(); } /** * Returns the secondary parents of this property map, for interfaces that * need multiple inheritance. */ Iterable<PropertyMap> getSecondaryParents() { if (parentSource == null) { return ImmutableList.of(); } Iterable<ObjectType> extendedInterfaces = parentSource.getCtorExtendedInterfaces(); // Most of the time, this will be empty. if (Iterables.isEmpty(extendedInterfaces)) { return ImmutableList.of(); } return Iterables.transform(extendedInterfaces, PROP_MAP_FROM_TYPE); } Property getSlot(String name) { if (properties.containsKey(name)) { return properties.get(name); } PropertyMap primaryParent = getPrimaryParent(); if (primaryParent != null) { Property prop = primaryParent.getSlot(name); if (prop != null) { return prop; } } for (PropertyMap p : getSecondaryParents()) { if (p != null) { Property prop = p.getSlot(name); if (prop != null) { return prop; } } } return null; } Property getOwnProperty(String propertyName) { return properties.get(propertyName); } int getPropertiesCount() { PropertyMap primaryParent = getPrimaryParent(); if (primaryParent == null) { return this.properties.size(); } Set<String> props = Sets.newHashSet(); collectPropertyNames(props); return props.size(); } boolean hasOwnProperty(String propertyName) { return properties.get(propertyName) != null; } boolean hasProperty(String propertyName) { return getSlot(propertyName) != null; } Set<String> getOwnPropertyNames() { return properties.keySet(); } void collectPropertyNames(Set<String> props) { for (String prop : properties.keySet()) { props.add(prop); } PropertyMap primaryParent = getPrimaryParent(); if (primaryParent != null) { primaryParent.collectPropertyNames(props); } for (PropertyMap p : getSecondaryParents()) { if (p != null) { p.collectPropertyNames(props); } } } boolean removeProperty(String name) { return properties.remove(name) != null; } void putProperty(String name, Property newProp) { Property oldProp = properties.get(name); if (oldProp != null) { // This is to keep previously inferred JsDoc info, e.g., in

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> far. But the compile-time // costs would be unacceptable. // // We do one pass where we do typed scope creation for all scopes // in pre-order. // // Then we do a second pass where we do all type inference // (type propagation) in pre-order. // // We use a memoized scope creator so that we never create a scope // more than once. // // This will allow us to handle cases like: // var ns = {}; // (function() { /** JSDoc */ ns.method = function() {}; })(); // ns.method(); // In this code, we need to build the symbol table for the inner scope in // order to propagate the type of ns.method in the outer scope. (new NodeTraversal( compiler, new FirstScopeBuildingCallback(), scopeCreator)) .traverseWithScope(node, topScope); for (Scope s : scopeCreator.getAllMemoizedScopes()) { s.resolveTypes(); } (new NodeTraversal( compiler, new SecondScopeBuildingCallback(), scopeCreator)) .traverseWithScope(node, topScope); } void inferScope(Node n, Scope scope) { TypeInference typeInference = new TypeInference( compiler, computeCfg(n), reverseInterpreter, scope, assertionFunctionsMap); try { typeInference.analyze(); // Resolve any new type names found during the inference. compiler.getTypeRegistry().resolveTypesInScope(scope); } catch (DataFlowAnalysis.MaxIterationsExceededException e) { compiler.report(JSError.make(n.getSourceFileName(), n, DATAFLOW_ERROR)); } } private class FirstScopeBuildingCallback extends AbstractScopedCallback { @Override public void enterScope(NodeTraversal t) { t.getScope(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // Do nothing } } private class SecondScopeBuildingCallback extends AbstractScopedCallback { @Override public void enterScope(NodeTraversal t) { // Only infer the entry root, rather than the scope root. // This ensures that incremental compilation only touches the root // that's been swapped out. inferScope(t.getCurrentNode(), t.getScope()); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // Do nothing } } private ControlFlowGraph<Node> computeCfg(Node n) { ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, false); cfa.process(null, n); return cfa.getCfg(); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.javascript.rhino.Node; /** * The {@code StaticScope} interface must be implemented by any object that * defines variables for the purposes of static analysis. It is distinguished * from the {@code Scriptable} class that Rhino normally uses to represent a * run-time scope. * * @param <T> The type of information stored about the slot */ public interface StaticScope<T> { /** * Returns the root node associated with this scope. May be null. */ Node getRootNode(); /** Returns the scope enclosing this one or null if none. */ StaticScope<T> getParentScope(); /** * Returns any defined slot within this scope for this name. This call * continues searching through parent scopes if a slot with this name is not * found in the current scope. * @param name The name of the variable slot to look up. * @return The defined slot for the variable, or {@code null} if no * definition exists. */ StaticSlot<T> getSlot(String name); /** Like {@code getSlot} but does not recurse into parent scopes. */ StaticSlot<T> getOwnSlot(String name); /** Returns the expected type of {@code this} in the current scope. */ T getTypeOfThis(); }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>CompilerPass pass); /** * Returns the root node of the AST, which includes both externs and source. */ abstract Node getRoot(); // TODO(bashir) It would be good to extract a single dumb data object with // only getters and setters that keeps all global information we keep for a // compiler instance. Then move some of the functions of this class there. /** * Updates the list of references for variables in global scope. * * @param refMapPatch Maps each variable to all of its references; may contain * references collected from the whole AST or only a SCRIPT sub-tree. * @param collectionRoot The root of sub-tree in which reference collection * has been done. This should either be a SCRIPT node (if collection is * done on a single file) or it is assumed that collection is on full AST. */ abstract void updateGlobalVarReferences(Map<Var, ReferenceCollection> refMapPatch, Node collectionRoot); /** * This can be used to get the list of all references to all global variables * based on all previous calls to {@code updateGlobalVarReferences}. * * @return The reference collection map associated to global scope variable. */ abstract GlobalVarReferenceMap getGlobalVarReferences(); /** * @return a CompilerInput that can be modified to add addition extern * definitions; */ abstract CompilerInput getSynthesizedExternsInput(); /** * @return a number in [0,1] range indicating an approximate progress of the * last compile. Note this should only be used as a hint and no assumptions * should be made on accuracy, even a completed compile may choose not to set * this to 1.0 at the end. */ public abstract double getProgress(); /** * Gets the last pass name set by setProgress. */ abstract String getLastPassName(); /** * Sets the progress percentage as well as the name of the last pass that * ran (if available). * @param progress A precentage expressed as a double in the range [0, 1]. * Use -1 if you just want to set the last pass name. */ abstract void setProgress(double progress, @Nullable String lastPassName); /** * The subdir js/ contains libraries of code that we inject * at compile-time only if requested by this function. * * Notice that these libraries will almost always create global symbols. * * @param resourceName The name of the library. For example, if "base" is * is specified, then we load js/base.js * @return If new code was injected, returns the last expression node of the * library. If the caller needs to add additional code, they should add * it as the next sibling of this node. If new code was not injected, * returns null. */ abstract Node ensureLibraryInjected(String resourceName); /** * Stores the "new" Rhino parse tree for a given source file. * @param sourceName The source file name. * @param astRoot The "new" Rhino parse tree. */ abstract void setOldParseTree(String sourceName, AstRoot astRoot); /** * Gets an old format parse

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.common.collect.ImmutableList; import com.google.javascript.rhino.Node; /** * A builder class for function and arrow types. * * If you need to build an interface constructor, * use {@link JSTypeRegistry#createInterfaceType}. * * @author nicksantos@google.com (Nick Santos) */ public final class FunctionBuilder { private final JSTypeRegistry registry; private String name = null; private Node sourceNode = null; private Node parametersNode = null; private JSType returnType = null; private JSType typeOfThis = null; private TemplateTypeMap templateTypeMap = null; private boolean inferredReturnType = false; private boolean isConstructor = false; private boolean isNativeType = false; public FunctionBuilder(JSTypeRegistry registry) { this.registry = registry; } /** Set the name of the function type. */ public FunctionBuilder withName(String name) { this.name = name; return this; } /** Set the source node of the function type. */ public FunctionBuilder withSourceNode(Node sourceNode) { this.sourceNode = sourceNode; return this; } /** Set the parameters of the function type from a FunctionParamBuilder. */ public FunctionBuilder withParams(FunctionParamBuilder params) { this.parametersNode = params

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.build(); return this; } /** * Set the parameters of the function type with a specially-formatted node. */ public FunctionBuilder withParamsNode(Node parametersNode) { this.parametersNode = parametersNode; return this; } /** Set the return type. */ public FunctionBuilder withReturnType(JSType returnType) { this.returnType = returnType; return this; } /** Set the return type and whether it's inferred. */ public FunctionBuilder withReturnType(JSType returnType, boolean inferred) { this.returnType = returnType; this.inferredReturnType = inferred; return this; } /** Sets an inferred return type. */ public FunctionBuilder withInferredReturnType(JSType returnType) { this.returnType = returnType; this.inferredReturnType = true; return this; } /** Set the "this" type. */ public FunctionBuilder withTypeOfThis(JSType typeOfThis) { this.typeOfThis = typeOfThis; return this; } /** Set the template name. */ public FunctionBuilder withTemplateKeys( ImmutableList<TemplateType> templateKeys) { this.templateTypeMap = registry.createTemplateTypeMap(templateKeys, null); return this; } /** Make this a constructor. */ public FunctionBuilder forConstructor() { this.isConstructor = true; return this; } /** Set whether this is a constructor. */ public FunctionBuilder setIsConstructor(boolean isConstructor) { this.isConstructor = isConstructor; return this; } /** Make this a native type. */ FunctionBuilder forNativeType() { this.isNativeType = true; return this; } /** Copies all the information from another function type. */ public FunctionBuilder copyFromOtherFunction(FunctionType otherType) { this.name = otherType.getReferenceName(); this.sourceNode = otherType.getSource(); this.parametersNode = otherType.getParametersNode(); this.returnType = otherType.getReturnType(); this.typeOfThis = otherType.getTypeOfThis(); this.templateTypeMap = otherType.getTemplateTypeMap(); this.isConstructor = otherType.isConstructor(); this.isNativeType = otherType.isNativeObjectType(); return this; } /** Construct a new function type. */ public FunctionType build() { return new FunctionType(registry, name, sourceNode, new ArrowType(registry, parametersNode, returnType, inferredReturnType), typeOfThis, templateTypeMap, isConstructor, isNativeType); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> */ private Scope getFunctionScope() { return cache.functionScope; } /** Whether this flows from a bottom scope. */ private boolean flowsFromBottom() { return getFunctionScope().isBottom(); } /** * Creates an entry lattice for the flow. */ public static LinkedFlowScope createEntryLattice(Scope scope) { return new LinkedFlowScope(new FlatFlowScopeCache(scope)); } @Override public void inferSlotType(String symbol, JSType type) { Preconditions.checkState(!frozen); lastSlot = new LinkedFlowSlot(symbol, type, lastSlot); depth++; cache.dirtySymbols.add(symbol); } @Override public void inferQualifiedSlot(Node node, String symbol, JSType bottomType, JSType inferredType) { Scope functionScope = getFunctionScope(); if (functionScope.isLocal()) { if (functionScope.getVar(symbol) == null && !functionScope.isBottom()) { functionScope.declare(symbol, node, bottomType, null); } inferSlotType(symbol, inferredType); } } @Override public JSType getTypeOfThis() { return cache.functionScope.getTypeOfThis(); } @Override public Node getRootNode() { return getFunctionScope().getRootNode(); } @Override public StaticScope<JSType> getParentScope() { return getFunctionScope().getParentScope(); } /** * Get the slot for the given symbol. */ @Override public StaticSlot<JSType> getSlot(String name) { if (cache.dirtySymbols.contains(name)) { for (LinkedFlowSlot slot = lastSlot; slot != null; slot = slot.parent) { if (slot.getName().equals(name)) { return slot; } } } return cache.getSlot(name); } @Override public StaticSlot<JSType> getOwnSlot(String name) { throw new UnsupportedOperationException(); } @Override public FlowScope createChildFlowScope() { frozen = true; if (depth > MAX_DEPTH) { if (flattened == null) { flattened = new FlatFlowScopeCache(this); } return new LinkedFlowScope(flattened); } return new LinkedFlowScope(this); } /** * Iterate through all the linked flow scopes before this one. * If there's one and only one slot defined between this scope * and the blind scope, return it. */ @Override public StaticSlot<JSType> findUniqueRefinedSlot(FlowScope blindScope) { StaticSlot<JSType> result = null; for (LinkedFlowScope currentScope = this; currentScope != blindScope; currentScope = currentScope.parent) { for (LinkedFlowSlot currentSlot = currentScope.lastSlot; currentSlot != null && (currentScope.parent == null || currentScope.parent.lastSlot != currentSlot); currentSlot = currentSlot.parent) { if (result == null) { result = currentSlot; } else if (!currentSlot.getName().equals(result.getName())) { return null; } } } return result

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>; } /** * Look through the given scope, and try to find slots where it doesn't * have enough type information. Then fill in that type information * with stuff that we've inferred in the local flow. */ @Override public void completeScope(StaticScope<JSType> staticScope) { Scope scope = (Scope) staticScope; for (Iterator<Var> it = scope.getVars(); it.hasNext();) { Var var = it.next(); if (var.isTypeInferred()) { JSType type = var.getType(); if (type == null || type.isUnknownType()) { JSType flowType = getSlot(var.getName()).getType(); var.setType(flowType); } } } } /** * Remove flow scopes that add nothing to the flow. */ // NOTE(nicksantos): This function breaks findUniqueRefinedSlot, because // findUniqueRefinedSlot assumes that this scope is a direct descendant // of blindScope. This is not necessarily true if this scope has been // optimize()d and blindScope has not. This should be fixed. For now, // we only use optimize() where we know that we won't have to do // a findUniqueRefinedSlot on it. @Override public LinkedFlowScope optimize() { LinkedFlowScope current; for (current = this; current.parent != null && current.lastSlot == current.parent.lastSlot; current = current.parent) {} return current; } /** Join the two FlowScopes. */ static class FlowScopeJoinOp extends JoinOp.BinaryJoinOp<FlowScope> { @SuppressWarnings("unchecked") @Override public FlowScope apply(FlowScope a, FlowScope b) { // To join the two scopes, we have to LinkedFlowScope linkedA = (LinkedFlowScope) a; LinkedFlowScope linkedB = (LinkedFlowScope) b; linkedA.frozen = true; linkedB.frozen = true; if (linkedA.optimize() == linkedB.optimize()) { return linkedA.createChildFlowScope(); } return new LinkedFlowScope(new FlatFlowScopeCache(linkedA, linkedB)); } } @Override public boolean equals(Object other) { if (other instanceof LinkedFlowScope) { LinkedFlowScope that = (LinkedFlowScope) other; if (this.optimize() == that.optimize()) { return true; } // If two flow scopes are in the same function, then they could have // two possible function scopes: the real one and the BOTTOM scope. // If they have different function scopes, we *should* iterate through all // the variables in each scope and compare. However, 99.9% of the time, // they're not equal. And the other .1% of the time, we can pretend // they're equal--this just means that data flow analysis will have // to propagate the entry lattice a little bit further than it // really needs to. Everything will still come out ok. if (this.getFunctionScope() != that.getFunctionScope()) { return false; } if (cache == that.cache) { // If the

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> two flow scopes have the same cache, then we can check // equality a lot faster: by just looking at the "dirty" elements // in the cache, and comparing them in both scopes. for (String name : cache.dirtySymbols) { if (diffSlots(getSlot(name), that.getSlot(name))) { return false; } } return true; } Map<String, StaticSlot<JSType>> myFlowSlots = allFlowSlots(); Map<String, StaticSlot<JSType>> otherFlowSlots = that.allFlowSlots(); for (StaticSlot<JSType> slot : myFlowSlots.values()) { if (diffSlots(slot, otherFlowSlots.get(slot.getName()))) { return false; } otherFlowSlots.remove(slot.getName()); } for (StaticSlot<JSType> slot : otherFlowSlots.values()) { if (diffSlots(slot, myFlowSlots.get(slot.getName()))) { return false; } } return true; } return false; } /** * Determines whether two slots are meaningfully different for the * purposes of data flow analysis. */ private boolean diffSlots(StaticSlot<JSType> slotA, StaticSlot<JSType> slotB) { boolean aIsNull = slotA == null || slotA.getType() == null; boolean bIsNull = slotB == null || slotB.getType() == null; if (aIsNull && bIsNull) { return false; } else if (aIsNull ^ bIsNull) { return true; } // Both slots and types must be non-null. return slotA.getType().differsFrom(slotB.getType()); } /** * Gets all the symbols that have been defined before this point * in the current flow. Does not return slots that have not changed during * the flow. * * For example, consider the code: * <code> * var x = 3; * function f() { * var y = 5; * y = 6; // FLOW POINT * var z = y; * return z; * } * </code> * A FlowScope at FLOW POINT will return a slot for y, but not * a slot for x or z. */ private Map<String, StaticSlot<JSType>> allFlowSlots() { Map<String, StaticSlot<JSType>> slots = Maps.newHashMap(); for (LinkedFlowSlot slot = lastSlot; slot != null; slot = slot.parent) { if (!slots.containsKey(slot.getName())) { slots.put(slot.getName(), slot); } } for (Map.Entry<String, StaticSlot<JSType>> symbolEntry : cache.symbols.entrySet()) { if (!slots.containsKey(symbolEntry.getKey())) { slots.put(symbolEntry.getKey(), symbolEntry.getValue()); } } return slots; } @Override public int hashCode() { throw new UnsupportedOperationException(); } /** * A static slot that can be used in a linked list. */ private static class LinkedFlowSlot extends SimpleSlot { final LinkedFlowSlot

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>HashSet(symbols.keySet()); symbolNames.addAll(slotsB.keySet()); for (String name : symbolNames) { StaticSlot<JSType> slotA = slotsA.get(name); StaticSlot<JSType> slotB = slotsB.get(name); JSType joinedType = null; if (slotB == null || slotB.getType() == null) { StaticSlot<JSType> fnSlot = joinedScopeB.getFunctionScope().getSlot(name); JSType fnSlotType = fnSlot == null ? null : fnSlot.getType(); if (fnSlotType == null) { // Case #1 -- already inserted. } else { // Case #3 joinedType = slotA.getType().getLeastSupertype(fnSlotType); } } else if (slotA == null || slotA.getType() == null) { StaticSlot<JSType> fnSlot = joinedScopeA.getFunctionScope().getSlot(name); JSType fnSlotType = fnSlot == null ? null : fnSlot.getType(); if (fnSlotType == null) { // Case #2 symbols.put(name, slotB); } else { // Case #4 joinedType = slotB.getType().getLeastSupertype(fnSlotType); } } else { // Case #5 joinedType = slotA.getType().getLeastSupertype(slotB.getType()); } if (joinedType != null) { symbols.put(name, new SimpleSlot(name, joinedType, true)); } } } /** * Get the slot for the given symbol. */ public StaticSlot<JSType> getSlot(String name) { if (symbols.containsKey(name)) { return symbols.get(name); } else { return functionScope.getSlot(name); } } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS><T> visitor) { return visitor.caseUnknownType(); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseUnknownType(this, that); } @Override String toStringHelper(boolean forAnnotations) { return getReferenceName(); } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode) { // nothing to define return true; } @Override public ObjectType getImplicitPrototype() { return null; } @Override public FunctionType getConstructor() { return null; } @Override public String getReferenceName() { return isChecked ? "??" : "?"; } @Override public String getDisplayName() { return "Unknown"; } @Override public boolean hasDisplayName() { return true; } @Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { return BooleanLiteralSet.BOTH; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { return this; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> }; } public static class LocationMapping { final String prefix; final String replacement; public LocationMapping(String prefix, String replacement) { this.prefix = prefix; this.replacement = replacement; } } private final SourceMapGenerator generator; private List<LocationMapping> prefixMappings = Collections.emptyList(); private final Map<String, String> sourceLocationFixupCache = Maps.newHashMap(); private SourceMap(SourceMapGenerator generator) { this.generator = generator; } public void addMapping( Node node, FilePosition outputStartPosition, FilePosition outputEndPosition) { String sourceFile = node.getSourceFileName(); // If the node does not have an associated source file or // its line number is -1, then the node does not have sufficient // information for a mapping to be useful. if (sourceFile == null || node.getLineno() < 0) { return; } sourceFile = fixupSourceLocation(sourceFile); String originalName = (String) node.getProp(Node.ORIGINALNAME_PROP); // Strangely, Rhino source lines are one based but columns are // zero based. // We don't change this for the v1 or v2 source maps but for // v3 we make them both 0 based. int lineBaseOffset = 1; if (generator instanceof SourceMapGeneratorV1 || generator instanceof SourceMapGeneratorV2) { lineBaseOffset = 0; } generator.addMapping( sourceFile, originalName, new FilePosition(node.getLineno() - lineBaseOffset, node.getCharno()), outputStartPosition, outputEndPosition); } /** * @param sourceFile The source file location to fixup. * @return a remapped source file. */ private String fixupSourceLocation(String sourceFile) { if (prefixMappings.isEmpty()) { return sourceFile; } String fixed = sourceLocationFixupCache.get(sourceFile); if (fixed != null) { return fixed; } // Replace the first prefix found with its replacement for (LocationMapping mapping : prefixMappings) { if (sourceFile.startsWith(mapping.prefix)) { fixed = mapping.replacement + sourceFile.substring( mapping.prefix.length()); break; } } // If none of the mappings match then use the original file path. if (fixed == null) { fixed = sourceFile; } sourceLocationFixupCache.put(sourceFile, fixed); return fixed; } public void appendTo(Appendable out, String name) throws IOException { generator.appendTo(out, name); } public void reset() { generator.reset(); sourceLocationFixupCache.clear(); } public void setStartingPosition(int offsetLine, int offsetIndex) { generator.setStartingPosition(offsetLine, offsetIndex); } public void setWrapperPrefix(String prefix) { generator.setWrapperPrefix(prefix); } public void validate(boolean validate) { generator.validate(validate); } /** * @param sourceMapLocationMappings */ public void setPrefixMappings(List<LocationMapping>

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> result.addAll(module.getInputs()); } return result; } private Collection<CompilerInput> createEntryPointInputs( DependencyOptions depOptions, List<CompilerInput> inputs, SortedDependencies<CompilerInput> sorter) throws MissingModuleException, MissingProvideException { Set<CompilerInput> entryPointInputs = Sets.newLinkedHashSet(); Map<String, JSModule> modulesByName = getModulesByName(); if (depOptions.shouldPruneDependencies()) { if (!depOptions.shouldDropMoochers()) { entryPointInputs.addAll(sorter.getInputsWithoutProvides()); } for (String entryPoint : depOptions.getEntryPoints()) { // An entry point is either formatted as: // 'foo.bar' - peg foo.bar to its current module // 'modC:foo.bar' - peg foo.bar to modC String inputName = entryPoint; int splitPoint = entryPoint.indexOf(':'); CompilerInput entryPointInput = null; if (splitPoint != -1) { String moduleName = entryPoint.substring(0, splitPoint); inputName = entryPoint.substring( Math.min(splitPoint + 1, entryPoint.length() - 1)); JSModule module = modulesByName.get(moduleName); if (module == null) { throw new MissingModuleException(moduleName); } else { entryPointInput = sorter.getInputProviding(inputName); entryPointInput.overrideModule(module); } } else { entryPointInput = sorter.getInputProviding(inputName); } entryPointInputs.add(entryPointInput); } CompilerInput baseJs = sorter.maybeGetInputProviding("goog"); if (baseJs != null) { entryPointInputs.add(baseJs); } } else { entryPointInputs.addAll(inputs); } return entryPointInputs; } LinkedDirectedGraph<JSModule, String> toGraphvizGraph() { LinkedDirectedGraph<JSModule, String> graphViz = LinkedDirectedGraph.create(); for (JSModule module : getAllModules()) { graphViz.createNode(module); for (JSModule dep : module.getDependencies()) { graphViz.createNode(dep); graphViz.connect(module, "->", dep); } } return graphViz; } /** * A module depth comparator that considers a deeper module to be "less than" * a shallower module. Uses module names to consistently break ties. */ private class InverseDepthComparator implements Comparator<JSModule> { @Override public int compare(JSModule m1, JSModule m2) { return depthCompare(m2, m1); } } private int depthCompare(JSModule m1, JSModule m2) { if (m1 == m2) { return 0; } int d1 = m1.getDepth(); int d2 = m2.getDepth(); return d1 < d2 ? -1 : d2 == d1 ? m1.getName().compareTo(m2.getName()) : 1; } /** * Exception class for declaring when the modules being fed

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>processing sanity check. private final boolean sanityCheck; // Whether extern checks emit error. private final boolean strictExternCheck; VarCheck(AbstractCompiler compiler) { this(compiler, false); } VarCheck(AbstractCompiler compiler, boolean sanityCheck) { this.compiler = compiler; this.strictExternCheck = compiler.getErrorLevel( JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR; this.sanityCheck = sanityCheck; } @Override public void process(Node externs, Node root) { // Don't run externs-checking in sanity check mode. Normalization will // remove duplicate VAR declarations, which will make // externs look like they have assigns. if (!sanityCheck) { NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck()); } NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); for (String varName : varsToDeclareInExterns) { createSynthesizedExternVar(varName); } } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { Preconditions.checkState(scriptRoot.isScript()); NodeTraversal t = new NodeTraversal(compiler, this); // Note we use the global scope to prevent wrong "undefined-var errors" on // variables that are defined in other JS files. t.traverseWithScope(scriptRoot, SyntacticScopeCreator.generateUntypedTopScope(compiler)); // TODO(bashir) Check if we need to createSynthesizedExternVar like process. } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isName()) { return; } String varName = n.getString(); // Only a function can have an empty name. if (varName.isEmpty()) { Preconditions.checkState(parent.isFunction()); Preconditions.checkState(NodeUtil.isFunctionExpression(parent)); return; } // Check if this is a declaration for a var that has been declared // elsewhere. If so, mark it as a duplicate. if ((parent.isVar() || NodeUtil.isFunctionDeclaration(parent)) && varsToDeclareInExterns.contains(varName)) { createSynthesizedExternVar(varName); n.addSuppression("duplicate"); } // Check that the var has been declared. Scope scope = t.getScope(); Scope.Var var = scope.getVar(varName); if (var == null) { if (NodeUtil.isFunctionExpression(parent)) { // e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the // current scope. } else { // The extern checks are stricter, don't report a second error. if (!strictExternCheck || !t.getInput().isExtern()) { t.report(n, UNDEFINED_VAR_ERROR, varName); } if (sanityCheck) { throw new IllegalStateException("Unexpected variable " + varName); } else { createSynthesizedExternVar(varName); scope.getGlobalScope().declare(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>varName, n, null, getSynthesizedExternsInput()); } } return; } CompilerInput currInput = t.getInput(); CompilerInput varInput = var.input; if (currInput == varInput || currInput == null || varInput == null) { // The variable was defined in the same file. This is fine. return; } // Check module dependencies. JSModule currModule = currInput.getModule(); JSModule varModule = varInput.getModule(); JSModuleGraph moduleGraph = compiler.getModuleGraph(); if (!sanityCheck && varModule != currModule && varModule != null && currModule != null) { if (moduleGraph.dependsOn(currModule, varModule)) { // The module dependency was properly declared. } else { if (scope.isGlobal()) { if (moduleGraph.dependsOn(varModule, currModule)) { // The variable reference violates a declared module dependency. t.report(n, VIOLATED_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } else { // The variable reference is between two modules that have no // dependency relationship. This should probably be considered an // error, but just issue a warning for now. t.report(n, MISSING_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } } else { t.report(n, STRICT_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } } } } /** * Create a new variable in a synthetic script. This will prevent * subsequent compiler passes from crashing. */ private void createSynthesizedExternVar(String varName) { Node nameNode = IR.name(varName); // Mark the variable as constant if it matches the coding convention // for constant vars. // NOTE(nicksantos): honestly, I'm not sure how much this matters. // AFAIK, all people who use the CONST coding convention also // compile with undeclaredVars as errors. We have some test // cases for this configuration though, and it makes them happier. if (compiler.getCodingConvention().isConstant(varName)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } getSynthesizedExternsRoot().addChildToBack( IR.var(nameNode)); varsToDeclareInExterns.remove(varName); compiler.reportCodeChange(); } /** * A check for name references in the externs inputs. These used to prevent * a variable from getting renamed, but no longer have any effect. */ private class NameRefInExternsCheck extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { switch (parent.getType()) { case Token.VAR: case Token.FUNCTION: case Token.PARAM_LIST: // These are okay. break; case Token.GETPROP: if (n == parent.getFirstChild()) { Scope scope = t.getScope(); Scope.Var var

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> = scope.getVar(n.getString()); if (var == null) { t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString()); varsToDeclareInExterns.add(n.getString()); } } break; default: t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString()); Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { varsToDeclareInExterns.add(n.getString()); } break; } } } } /** Lazily create a "new" externs input for undeclared variables. */ private CompilerInput getSynthesizedExternsInput() { return compiler.getSynthesizedExternsInput(); } /** Lazily create a "new" externs root for undeclared variables. */ private Node getSynthesizedExternsRoot() { if (synthesizedExternsRoot == null) { CompilerInput synthesizedExterns = getSynthesizedExternsInput(); synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler); } return synthesizedExternsRoot; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> = t.getScopeRoot(); if (n.isName()) { if (noLocalVariables) { return null; } key = n.getQualifiedName(); if (scopeNode.isFunction()) { JSType parentScopeType = t.getScope().getParentScope().getTypeOfThis(); /* * If the locally defined variable is defined within a function, use * the function name to create ID. */ if (!parentScopeType.isGlobalThisType()) { key = parentScopeType.toString() + "~" + key; } key = NodeUtil.getFunctionName(scopeNode) + "=" + key; } } else { /* * Only handle cases such as a.b.c.X and not cases where the * eventful object is stored in an array or uses a function to * determine the index. * * Note: Inheritance changes the name that should be returned here */ if (!n.isQualifiedName()) { return null; } key = n.getQualifiedName(); /* * If it is not a simple variable and doesn't use this, then we assume * global variable. */ Node base = getBase(n); if (base != null && base.isThis()) { if (base.getJSType().isUnknownType()) { // Handle anonymous function created in constructor: // // /** // * @extends {goog.SubDisposable} // * @constructor */ // speel.Person = function() { // this.run = function() { // this.eh = new goog.events.EventHandler(); // } //}; key = t.getScope().getParentScope().getTypeOfThis().toString() + "~" + key; } else { if (n.getFirstChild() == null) { key = base.getJSType().toString() + "=" + key; } else { ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); if (objectType == null) { return null; } ObjectType hObjT = objectType; String propertyName = n.getLastChild().getString(); while (objectType != null) { hObjT = objectType; objectType = objectType.getImplicitPrototype(); if (objectType == null) { break; } if (objectType.getDisplayName().endsWith("prototype")) { continue; } if (!objectType.getPropertyNames().contains(propertyName)) { break; } } key = hObjT.toString() + "=" + key; } } } } return key; } @Override public void process(Node externs, Node root) { // This pass should not have gotten added in this case Preconditions.checkArgument(checkingPolicy != DisposalCheckingPolicy.OFF); // Initialize types googDisposableType = compiler.getTypeRegistry().getType(DISPOSABLE_TYPE_NAME); googEventsEventHandlerType = compiler.getTypeRegistry().getType(EVENT_HANDLER_TYPE_NAME); /* * Required types not found therefore the kind of pattern considered * will not be found. */ if (googEventsEventHandlerType == null || googDisposableType == null) { return;

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>String s : order) { if (eventfulTypes.contains(typeRegistry.getType(s))) { for (String v : eventizes.get(s)) { eventfulTypes.add(typeRegistry.getType(v)); } } } } private JSType maybeReturnDisposedType(Node n, boolean checkDispose) { /* * Checks for: * - Y.registerDisposable(X) * (Y has to be of type goog.Disposable) * - X.dispose() * - goog.dispose(X) * - X.removeAll() (X is of type goog.events.EventHandler) * - <array>.property(X) or Y.push(X) */ Node first = n.getFirstChild(); if (first == null || !first.isQualifiedName()) { return null; } String property = first.getQualifiedName(); if (property.endsWith(".registerDisposable")) { /* * Ensure object is of type disposable */ Node base = first.getFirstChild(); JSType baseType = base.getJSType(); if (baseType == null || !isPossiblySubtype(baseType, googDisposableType)) { return null; } return n.getLastChild().getJSType(); } if (checkDispose) { if (property.equals("goog.dispose")) { return n.getLastChild().getJSType(); } if (property.endsWith(".dispose")) { /* * n -> call * n.firstChild -> "dispose" * n.firstChild.firstChild -> object */ return n.getFirstChild().getFirstChild().getJSType(); } } return null; } /* * Compute eventize relationship graph. */ private class ComputeEventizeTraversal extends AbstractPostOrderCallback implements ScopedCallback { /* * Keep track of whether in the constructor or disposal scope. */ Stack<Boolean> isConstructorStack; Stack<Boolean> isDisposalStack; public ComputeEventizeTraversal() { isConstructorStack = new Stack<Boolean>(); isDisposalStack = new Stack<Boolean>(); eventizes = new HashMap<String, Set<String>>(); } private Boolean inConstructorScope() { Preconditions.checkNotNull(isConstructorStack); if (isDisposalStack.size() > 0) { return isConstructorStack.peek(); } return null; } private Boolean inDisposalScope() { Preconditions.checkNotNull(isDisposalStack); if (isDisposalStack.size() > 0) { return isDisposalStack.peek(); } return null; } /* * Filter types not interested in for eventize graph */ private boolean collectorFilterType(JSType type) { if (type == null) { return true; } if (type.isEmptyType() || type.isUnknownType() || !isPossiblySubtype(type, googDisposableType)) { return true; } return false; } /* * Log that thisType eventizes thatType. */ private void addEventize(JSType thisType, JSType thatType) { if (collectorFilterType(thisType

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>) || collectorFilterType(thatType) || thisType.isEquivalentTo(thatType)) { return; } String className = thisType.getDisplayName(); if (thatType.isUnionType()) { UnionType ut = thatType.toMaybeUnionType(); for (JSType type : ut.getAlternates()) { if (type.isObject()) { addEventizeClass(className, type); } } } else { addEventizeClass(className, thatType); } } private void addEventizeClass(String className, JSType thatType) { String propertyJsTypeName = thatType.getDisplayName(); Set<String> eventize = eventizes.get(propertyJsTypeName); if (eventize == null) { eventize = new HashSet<String>(); eventizes.put(propertyJsTypeName, eventize); } eventize.add(className); } @Override public void enterScope(NodeTraversal t) { Node n = t.getScopeRoot(); boolean isConstructor = false; boolean isInDisposal = false; String functionName = null; /* * Scope entered is a function definition */ if (n.isFunction()) { functionName = NodeUtil.getFunctionName(n); /* * Skip anonymous functions */ if (functionName != null) { JSDocInfo jsDocInfo = NodeUtil.getBestJSDocInfo(n); if (jsDocInfo != null) { /* * Record constructor of a type */ if (jsDocInfo.isConstructor()) { isConstructor = true; /* * Initialize eventizes relationship */ if (t.getScope() != null && t.getScope().getTypeOfThis() != null) { ObjectType objectType = ObjectType.cast(t.getScope().getTypeOfThis().dereference()); /* * Eventize due to inheritance */ while (objectType != null) { objectType = objectType.getImplicitPrototype(); if (objectType == null) { break; } if (objectType.getDisplayName().endsWith("prototype")) { continue; } addEventize(compiler.getTypeRegistry().getType(functionName), objectType); /* * Don't add transitive eventize edges here, it will be * taken care of in computeEventful */ break; } } } } /* * Indicate within a disposeInternal member */ if (functionName.endsWith(".disposeInternal")) { isInDisposal = true; } } isConstructorStack.push(isConstructor); isDisposalStack.push(isInDisposal); } else { isConstructorStack.push(inConstructorScope()); isDisposalStack.push(inDisposalScope()); } } @Override public void exitScope(NodeTraversal t) { isConstructorStack.pop(); isDisposalStack.pop(); } /* * Is the current node a call to goog.events.unlisten */ private void isGoogEventsUnlisten(Node n) { Preconditions.checkArgument(n.getChildCount() > 3); Node listener = n.getChildAtIndex(3); Node object

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>WithListener = n.getChildAtIndex(1); if (!objectWithListener.isQualifiedName()) { return; } if (listener.isFunction()) { /* * Anonymous function */ compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } else if (listener.isCall()) { if (!listener.getFirstChild().isQualifiedName()) { /* * Anonymous function */ compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } else if (listener.getFirstChild().getQualifiedName().equals("goog.bind")) { /* * Using goog.bind to unlisten */ compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } } } private void isCalled(NodeTraversal t, Node n) { Node functionCalled = n.getFirstChild(); if (functionCalled == null || !functionCalled.isQualifiedName()) { return; } String functionCalledName = functionCalled.getQualifiedName(); JSType typeOfThis = getTypeOfThisForScope(t); if (typeOfThis == null) { return; } /* * Class considered eventful if there is an unlisten call in the * disposal. */ if (functionCalledName.equals("goog.events.unlisten")) { if (inDisposalScope()) { eventfulTypes.add(typeOfThis); } isGoogEventsUnlisten(n); } if (inDisposalScope() && functionCalledName.equals("goog.events.removeAll")) { eventfulTypes.add(typeOfThis); } /* * If member with qualified name gets disposed of when this class * gets disposed, consider the member type as an eventizer of this * class. */ JSType disposedType = maybeReturnDisposedType(n, inDisposalScope()); if (!collectorFilterType(disposedType)) { addEventize(getTypeOfThisForScope(t), disposedType); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: isCalled(t, n); break; default: break; } } } private class Traversal extends AbstractPostOrderCallback implements ScopedCallback { /* * Checks if the input node correspond to the creation of an eventful object */ private boolean createsEventfulObject(Node n) { Node first = n.getFirstChild(); JSType type = n.getJSType(); if (first == null || !first.isQualifiedName() || type.isEmptyType() || type.isUnknownType()) { return false; } boolean isOfTypeNeedingDisposal = false; for (JSType disposableType : eventfulTypes) { if (type.isSubtype(disposableType)) { isOfTypeNeedingDisposal = true; break; } } return isOfTypeNeedingDisposal; } /* * This function traverses the current scope to see if a locally *

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>) { return; } EventfulObjectState e = eventfulObjectMap.get(key); if (e == null) { e = new EventfulObjectState(); eventfulObjectMap.put(key, e); } e.seen = SeenType.POSSIBLY_DISPOSED; } @Override public void enterScope(NodeTraversal t) { /* * Local variables captured in scope are filtered at present. * LiveVariableAnalysis used to filter such variables. */ ControlFlowGraph<Node> cfg = t.getControlFlowGraph(); LiveVariablesAnalysis liveness = new LiveVariablesAnalysis(cfg, t.getScope(), compiler); liveness.analyze(); for (Var v : liveness.getEscapedLocals()) { eventfulObjectDisposed(t, v.getNode()); } } @Override public void exitScope(NodeTraversal t) { } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.ASSIGN: isAssign(t, n); break; case Token.CALL: isCall(t, n); break; case Token.FUNCTION: isFunction(t, n); break; case Token.NEW: isNew(t, n, parent); break; case Token.RETURN: isReturn(t, n); break; default: break; } } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.javascript.jscomp.NodeTraversal.ScopedCallback; import com.google.javascript.jscomp.Scope.Var; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.StaticReference; import com.google.javascript.rhino.jstype.StaticSourceFile; import com.google.javascript.rhino.jstype.StaticSymbolTable; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * A helper class for passes that want to access all information about where a * variable is referenced and declared at once and then make a decision as to * how it should be handled, possibly inlining, reordering, or generating * warnings. Callers do this by providing {@link Behavior} and then * calling {@link #process(Node, Node)}. * * @author kushal@google.com (Kushal Dave) */ class ReferenceCollectingCallback implements ScopedCallback, HotSwapCompilerPass, StaticSymbolTable<Var, ReferenceCollectingCallback.Reference> { /** * Maps a given variable to a collection of references to that name. Note that * Var objects are not stable across multiple traversals (unlike scope root or * name). */ private final Map<Var, ReferenceCollection> referenceMap = Maps.newHashMap(); /** * The stack of basic blocks and scopes the current traversal is in. */ private final Deque<BasicBlock> blockStack = new ArrayDeque<BasicBlock>(); /** * Source of behavior at various points in the traversal. */ private final Behavior behavior; /** * JavaScript compiler to use in traversing. */ private final AbstractCompiler compiler; /** * Only collect references for filtered variables. */

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> private final Predicate<Var> varFilter; /** * Constructor initializes block stack. */ ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior) { this(compiler, behavior, Predicates.<Var>alwaysTrue()); } /** * Constructor only collects references that match the given variable. * * The test for Var equality uses reference equality, so it's necessary to * inject a scope when you traverse. */ ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior, Predicate<Var> varFilter) { this.compiler = compiler; this.behavior = behavior; this.varFilter = varFilter; } /** * Convenience method for running this pass over a tree with this * class as a callback. */ @Override public void process(Node externs, Node root) { NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); } /** * Same as process but only runs on a part of AST associated to one script. */ @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { NodeTraversal.traverse(compiler, scriptRoot, this); } /** * Gets the variables that were referenced in this callback. */ @Override public Iterable<Var> getAllSymbols() { return referenceMap.keySet(); } @Override public Scope getScope(Var var) { return var.scope; } /** * Gets the reference collection for the given variable. */ @Override public ReferenceCollection getReferences(Var v) { return referenceMap.get(v); } /** * For each node, update the block stack and reference collection * as appropriate. */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { Var v; if (n.getString().equals("arguments")) { v = t.getScope().getArgumentsVar(); } else { v = t.getScope().getVar(n.getString()); } if (v != null && varFilter.apply(v)) { addReference(v, new Reference(n, t, blockStack.peek())); } } if (isBlockBoundary(n, parent)) { blockStack.pop(); } } /** * Updates block stack and invokes any additional behavior. */ @Override public void enterScope(NodeTraversal t) { Node n = t.getScope().getRootNode(); BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek(); blockStack.push(new BasicBlock(parent, n)); } /** * Updates block stack and invokes any additional behavior. */ @Override public void exitScope(NodeTraversal t) { blockStack.pop(); if (t.getScope().isGlobal()) { // Update global scope reference lists when we are done with it. compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot()); behavior.afterExitScope(t, compiler.getGlobalVarReferences()); } else { behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap)); } } /** * Updates block stack. */ @

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // If node is a new basic block, put on basic block stack if (isBlockBoundary(n, parent)) { blockStack.push(new BasicBlock(blockStack.peek(), n)); } return true; } /** * @return true if this node marks the start of a new basic block */ private static boolean isBlockBoundary(Node n, Node parent) { if (parent != null) { switch (parent.getType()) { case Token.DO: case Token.FOR: case Token.TRY: case Token.WHILE: case Token.WITH: // NOTE: TRY has up to 3 child blocks: // TRY // BLOCK // BLOCK // CATCH // BLOCK // Note that there is an explicit CATCH token but no explicit // FINALLY token. For simplicity, we consider each BLOCK // a separate basic BLOCK. return true; case Token.AND: case Token.HOOK: case Token.IF: case Token.OR: // The first child of a conditional is not a boundary, // but all the rest of the children are. return n != parent.getFirstChild(); } } return n.isCase(); } private void addReference(Var v, Reference reference) { // Create collection if none already ReferenceCollection referenceInfo = referenceMap.get(v); if (referenceInfo == null) { referenceInfo = new ReferenceCollection(); referenceMap.put(v, referenceInfo); } // Add this particular reference referenceInfo.add(reference); } interface ReferenceMap { ReferenceCollection getReferences(Var var); } private static class ReferenceMapWrapper implements ReferenceMap { private final Map<Var, ReferenceCollection> referenceMap; public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) { this.referenceMap = referenceMap; } @Override public ReferenceCollection getReferences(Var var) { return referenceMap.get(var); } } /** * Way for callers to add specific behavior during traversal that * utilizes the built-up reference information. */ interface Behavior { /** * Called after we finish with a scope. */ void afterExitScope(NodeTraversal t, ReferenceMap referenceMap); } static final Behavior DO_NOTHING_BEHAVIOR = new Behavior() { @Override public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {} }; /** * A collection of references. Can be subclassed to apply checks or * store additional state when adding. */ static class ReferenceCollection implements Iterable<Reference> { List<Reference> references = Lists.newArrayList(); @Override public Iterator<Reference> iterator() { return references.iterator(); } void add(Reference reference) { references.add(reference); } /** * Determines if the variable for this reference collection is * "well-defined." A variable is well-defined if we can prove at * compile-time that it's assigned a value before it's used. * * Notice that if this function returns false, this doesn't imply

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> that the * variable is used before it's assigned. It just means that we don't * have enough information to make a definitive judgment. */ protected boolean isWellDefined() { int size = references.size(); if (size == 0) { return false; } // If this is a declaration that does not instantiate the variable, // it's not well-defined. Reference init = getInitializingReference(); if (init == null) { return false; } Preconditions.checkState(references.get(0).isDeclaration()); BasicBlock initBlock = init.getBasicBlock(); for (int i = 1; i < size; i++) { if (!initBlock.provablyExecutesBefore( references.get(i).getBasicBlock())) { return false; } } return true; } /** * Whether the variable is escaped into an inner scope. */ boolean isEscaped() { Scope scope = null; for (Reference ref : references) { if (scope == null) { scope = ref.scope; } else if (scope != ref.scope) { return true; } } return false; } /** * @param index The index into the references array to look for an * assigning declaration. * * This is either the declaration if a value is assigned (such as * "var a = 2", "function a()...", "... catch (a)..."). */ private boolean isInitializingDeclarationAt(int index) { Reference maybeInit = references.get(index); if (maybeInit.isInitializingDeclaration()) { // This is a declaration that represents the initial value. // Specifically, var declarations without assignments such as "var a;" // are not. return true; } return false; } /** * @param index The index into the references array to look for an * initialized assignment reference. That is, an assignment immediately * follow a variable declaration that itself does not initialize the * variable. */ private boolean isInitializingAssignmentAt(int index) { if (index < references.size() && index > 0) { Reference maybeDecl = references.get(index - 1); if (maybeDecl.isVarDeclaration()) { Preconditions.checkState(!maybeDecl.isInitializingDeclaration()); Reference maybeInit = references.get(index); if (maybeInit.isSimpleAssignmentToName()) { return true; } } } return false; } /** * @return The reference that provides the value for the variable at the * time of the first read, if known, otherwise null. * * This is either the variable declaration ("var a = ...") or first * reference following the declaration if it is an assignment. */ Reference getInitializingReference() { if (isInitializingDeclarationAt(0)) { return references.get(0); } else if (isInitializingAssignmentAt(1)) { return references.get(1); } return null; } /** * Constants are allowed to be defined after their first use. */ Reference getInitializingReferenceForConstants() { int size = references.size(); for (int i = 0; i

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> < size; i++) { if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) { return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { Reference assignment = null; int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { if (assignment == null) { assignment = ref; } else { return null; } } } return assignment; } /** * @return Whether the variable is never assigned a value. */ boolean isNeverAssigned() { int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { return false; } } return true; } boolean firstReferenceIsAssigningDeclaration() { int size = references.size(); if (size > 0 && references.get(0).isInitializingDeclaration()) { return true; } return false; } } /** * Represents a single declaration or reference to a variable. */ static final class Reference implements StaticReference<JSType> { private static final Set<Integer> DECLARATION_PARENTS = ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH); private final Node nameNode; private final BasicBlock basicBlock; private final Scope scope; private final InputId inputId; private final StaticSourceFile sourceFile; Reference(Node nameNode, NodeTraversal t, BasicBlock basicBlock) { this(nameNode, basicBlock, t.getScope(), t.getInput().getInputId()); } // Bleeding functions are weird, because the declaration does // not appear inside their scope. So they need their own constructor. static Reference newBleedingFunction(NodeTraversal t, BasicBlock basicBlock, Node func) { return new Reference(func.getFirstChild(), basicBlock, t.getScope(), t.getInput().getInputId()); } /** * Creates a variable reference in a given script file name, used in tests. * * @return The created reference. */ @VisibleForTesting static Reference createRefForTest(CompilerInput input) { return new Reference(new Node(Token.NAME), null,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> null, input.getInputId()); } private Reference(Node nameNode, BasicBlock basicBlock, Scope scope, InputId inputId) { this.nameNode = nameNode; this.basicBlock = basicBlock; this.scope = scope; this.inputId = inputId; this.sourceFile = nameNode.getStaticSourceFile(); } /** * Makes a copy of the current reference using a new Scope instance. */ Reference cloneWithNewScope(Scope newScope) { return new Reference(nameNode, basicBlock, newScope, inputId); } @Override public Var getSymbol() { return scope.getVar(nameNode.getString()); } @Override public Node getNode() { return nameNode; } public InputId getInputId() { return inputId; } @Override public StaticSourceFile getSourceFile() { return sourceFile; } boolean isDeclaration() { Node parent = getParent(); Node grandparent = parent.getParent(); return DECLARATION_PARENTS.contains(parent.getType()) || parent.isParamList() && grandparent.isFunction(); } boolean isVarDeclaration() { return getParent().isVar(); } boolean isHoistedFunction() { return NodeUtil.isHoistedFunctionDeclaration(getParent()); } /** * Determines whether the variable is initialized at the declaration. */ boolean isInitializingDeclaration() { // VAR is the only type of variable declaration that may not initialize // its variable. Catch blocks, named functions, and parameters all do. return isDeclaration() && !getParent().isVar() || nameNode.getFirstChild() != null; } /** * @return For an assignment, variable declaration, or function declaration * return the assigned value, otherwise null. */ Node getAssignedValue() { Node parent = getParent(); return (parent.isFunction()) ? parent : NodeUtil.getAssignedValue(nameNode); } BasicBlock getBasicBlock() { return basicBlock; } Node getParent() { return getNode().getParent(); } Node getGrandparent() { Node parent = getParent(); return parent == null ? null : parent.getParent(); } private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.isVar()) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; } boolean isSimpleAssignmentToName() { Node parent = getParent(); return parent.isAssign() && parent.getFirstChild() == nameNode; } boolean isLvalue() { Node parent = getParent(); int parentType = parent.getType(); return (parentType == Token.VAR && nameNode.getFirstChild() != null) || parentType == Token.INC || parentType == Token.DEC || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == nameNode) || isLhsOfForInExpression(nameNode); } Scope getScope() { return scope; } } /** * Represents a section of code that is uninterrupted by control

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> structures * (conditional or iterative logic). */ static final class BasicBlock { private final BasicBlock parent; /** * Determines whether the block may not be part of the normal control flow, * but instead "hoisted" to the top of the scope. */ private final boolean isHoisted; /** * Whether this block denotes a function scope. */ private final boolean isFunction; /** * Whether this block denotes a loop. */ private final boolean isLoop; /** * Creates a new block. * @param parent The containing block. * @param root The root node of the block. */ BasicBlock(BasicBlock parent, Node root) { this.parent = parent; // only named functions may be hoisted. this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root); this.isFunction = root.isFunction(); if (root.getParent() != null) { int pType = root.getParent().getType(); this.isLoop = pType == Token.DO || pType == Token.WHILE || pType == Token.FOR; } else { this.isLoop = false; } } BasicBlock getParent() { return parent; } /** * Determines whether this block is equivalent to the very first block that * is created when reference collection traversal enters global scope. Note * that when traversing a single script in a hot-swap fashion a new instance * of {@code BasicBlock} is created. * * @return true if this is global scope block. */ boolean isGlobalScopeBlock() { return getParent() == null; } /** * Determines whether this block is guaranteed to begin executing before * the given block does. */ boolean provablyExecutesBefore(BasicBlock thatBlock) { // If thatBlock is a descendant of this block, and there are no hoisted // blocks between them, then this block must start before thatBlock. BasicBlock currentBlock; for (currentBlock = thatBlock; currentBlock != null && currentBlock != this; currentBlock = currentBlock.getParent()) { if (currentBlock.isHoisted) { return false; } } if (currentBlock == this) { return true; } if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) { return true; } return false; } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Root, false); } /** Main entry point of this phase for testing code. */ public Scope processForTesting(Node externsRoot, Node jsRoot) { Preconditions.checkState(scopeCreator == null); Preconditions.checkState(topScope == null); Preconditions.checkState(jsRoot.getParent() != null); Node externsAndJsRoot = jsRoot.getParent(); scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = scopeCreator.createScope(externsAndJsRoot, null); TypeInferencePass inference = new TypeInferencePass(compiler, reverseInterpreter, topScope, scopeCreator); inference.process(externsRoot, jsRoot); process(externsRoot, jsRoot); return topScope; } public void check(Node node, boolean externs) { Preconditions.checkNotNull(node); NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator); inExterns = externs; t.traverseWithScope(node, topScope); if (externs) { inferJSDocInfo.process(node, null); } else { inferJSDocInfo.process(null, node); } } private void checkNoTypeCheckSection(Node n, boolean enterSection) { switch (n.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.VAR: case Token.FUNCTION: case Token.ASSIGN: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isNoTypeCheck()) { if (enterSection) { noTypeCheckSection++; } else { noTypeCheckSection--; } } validator.setShouldReport(noTypeCheckSection == 0); break; } } private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType, String... arguments) { if (noTypeCheckSection == 0) { t.report(n, diagnosticType, arguments); } } @Override public boolean shouldTraverse( NodeTraversal t, Node n, Node parent) { checkNoTypeCheckSection(n, true); switch (n.getType()) { case Token.FUNCTION: // normal type checking final Scope outerScope = t.getScope(); final String functionPrivateName = n.getFirstChild().getString(); if (functionPrivateName != null && functionPrivateName.length() > 0 && outerScope.isDeclared(functionPrivateName, false) && // Ideally, we would want to check whether the type in the scope // differs from the type being defined, but then the extern // redeclarations of built-in types generates spurious warnings. !(outerScope.getVar( functionPrivateName).getType() instanceof FunctionType)) { report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName); } // TODO(user): Only traverse the function's body. The function's // name and arguments are traversed by the scope creator, and ideally // should not be traversed by the type checker. break; } return true; } /** * This is the meat of the type checking. It is basically one big

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> switch, * with each case representing one type of parse tree node. The individual * cases are usually pretty straightforward. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. */ @Override public void visit(NodeTraversal t, Node n, Node parent) { JSType childType; JSType leftType, rightType; Node left, right; // To be explicitly set to false if the node is not typeable. boolean typeable = true; switch (n.getType()) { case Token.CAST: Node expr = n.getFirstChild(); JSType exprType = getJSType(expr); JSType castType = getJSType(n); // TODO(johnlenz): determine if we can limit object literals in some // way. if (!expr.isObjectLit()) { validator.expectCanCast(t, n, castType, exprType); } ensureTyped(t, n, castType); if (castType.isSubtype(exprType) || expr.isObjectLit()) { expr.setJSType(castType); } break; case Token.NAME: typeable = visitName(t, n, parent); break; case Token.PARAM_LIST: typeable = false; break; case Token.COMMA: ensureTyped(t, n, getJSType(n.getLastChild())); break; case Token.TRUE: case Token.FALSE: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.THIS: ensureTyped(t, n, t.getScope().getTypeOfThis()); break; case Token.NULL: ensureTyped(t, n, NULL_TYPE); break; case Token.NUMBER: ensureTyped(t, n, NUMBER_TYPE); break; case Token.STRING: ensureTyped(t, n, STRING_TYPE); break; case Token.STRING_KEY: typeable = false; break; case Token.GETTER_DEF: case Token.SETTER_DEF: // Object literal keys are handled with OBJECTLIT break; case Token.ARRAYLIT: ensureTyped(t, n, ARRAY_TYPE); break; case Token.REGEXP: ensureTyped(t, n, REGEXP_TYPE); break; case Token.GETPROP: visitGetProp(t, n, parent); typeable = !(parent.isAssign() && parent.getFirstChild() == n); break; case Token.GETELEM: visitGetElem(t, n); // The type of GETELEM is always unknown, so no point counting that. // If that unknown leaks elsewhere (say by an assignment to another // variable), then it will be counted. typeable = false; break; case Token.VAR: visitVar(t, n); typeable = false; break; case Token.NEW: visitNew(t, n); break; case Token.CALL: visitCall(t,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> n); typeable = !parent.isExprResult(); break; case Token.RETURN: visitReturn(t, n); typeable = false; break; case Token.DEC: case Token.INC: left = n.getFirstChild(); checkPropCreation(t, left); validator.expectNumber(t, left, getJSType(left), "increment/decrement"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.NOT: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.VOID: ensureTyped(t, n, VOID_TYPE); break; case Token.TYPEOF: ensureTyped(t, n, STRING_TYPE); break; case Token.BITNOT: childType = getJSType(n.getFirstChild()); if (!childType.matchesInt32Context()) { report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()), childType.toString()); } ensureTyped(t, n, NUMBER_TYPE); break; case Token.POS: case Token.NEG: left = n.getFirstChild(); validator.expectNumber(t, left, getJSType(left), "sign operator"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: { left = n.getFirstChild(); right = n.getLastChild(); if (left.isTypeOf()) { if (right.isString()) { checkTypeofString(t, right, right.getString()); } } else if (right.isTypeOf() && left.isString()) { checkTypeofString(t, left, left.getString()); } leftType = getJSType(left); rightType = getJSType(right); // We do not want to warn about explicit comparisons to VOID. People // often do this if they think their type annotations screwed up. // // We do want to warn about cases where people compare things like // (Array|null) == (Function|null) // because it probably means they screwed up. // // This heuristic here is not perfect, but should catch cases we // care about without too many false negatives. JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined(); JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined(); TernaryValue result = TernaryValue.UNKNOWN; if (n.getType() == Token.EQ || n.getType() == Token.NE) { result = leftTypeRestricted.testForEquality(rightTypeRestricted); if (n.isNE()) { result = result.not(); } } else { // SHEQ or SHNE if (!leftTypeRestricted.canTestForShallowEqualityWith( rightTypeRestricted)) { result = n.getType() == Token.SHEQ ? TernaryValue.FALSE : TernaryValue.TRUE; } } if (result != TernaryValue.UNKNOWN) { report(t, n, DETERMINISTIC_TEST, leftType.toString(), rightType

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>BITAND: case Token.SUB: case Token.ADD: case Token.MUL: visitBinaryOperator(n.getType(), t, n); break; case Token.DELPROP: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.CASE: JSType switchType = getJSType(parent.getFirstChild()); JSType caseType = getJSType(n.getFirstChild()); validator.expectSwitchMatchesCase(t, n, switchType, caseType); typeable = false; break; case Token.WITH: { Node child = n.getFirstChild(); childType = getJSType(child); validator.expectObject(t, child, childType, "with requires an object"); typeable = false; break; } case Token.FUNCTION: visitFunction(t, n); break; // These nodes have no interesting type behavior. case Token.LABEL: case Token.LABEL_NAME: case Token.SWITCH: case Token.BREAK: case Token.CATCH: case Token.TRY: case Token.SCRIPT: case Token.EXPR_RESULT: case Token.BLOCK: case Token.EMPTY: case Token.DEFAULT_CASE: case Token.CONTINUE: case Token.DEBUGGER: case Token.THROW: typeable = false; break; // These nodes require data flow analysis. case Token.DO: case Token.IF: case Token.WHILE: typeable = false; break; case Token.FOR: if (NodeUtil.isForIn(n)) { Node obj = n.getChildAtIndex(1); if (getJSType(obj).isStruct()) { report(t, obj, IN_USED_WITH_STRUCT); } } typeable = false; break; // These nodes are typed during the type inference. case Token.AND: case Token.HOOK: case Token.OBJECTLIT: case Token.OR: if (n.getJSType() != null) { // If we didn't run type inference. ensureTyped(t, n); } else { // If this is an enum, then give that type to the objectlit as well. if ((n.isObjectLit()) && (parent.getJSType() instanceof EnumType)) { ensureTyped(t, n, parent.getJSType()); } else { ensureTyped(t, n); } } if (n.isObjectLit()) { JSType typ = getJSType(n); for (Node key : n.children()) { visitObjLitKey(t, key, n, typ); } } break; default: report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType())); ensureTyped(t, n); break; } // Don't count externs since the user's code may not even use that part. typeable = typeable && !inExterns; if (typeable) { doPercentTypedAccounting(t, n); } checkNoTypeCheckSection(n, false); } private void checkTypeofString(NodeTraversal t, Node n, String s

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>) { if (!(s.equals("number") || s.equals("string") || s.equals("boolean") || s.equals("undefined") || s.equals("function") || s.equals("object") || s.equals("unknown"))) { validator.expectValidTypeofName(t, n, s); } } /** * Counts the given node in the typed statistics. * @param n a node that should be typed */ private void doPercentTypedAccounting(NodeTraversal t, Node n) { JSType type = n.getJSType(); if (type == null) { nullCount++; } else if (type.isUnknownType()) { if (reportUnknownTypes) { compiler.report(t.makeError(n, UNKNOWN_EXPR_TYPE)); } unknownCount++; } else { typedCount++; } } /** * Visits an assignment <code>lvalue = rvalue</code>. If the * <code>lvalue</code> is a prototype modification, we change the schema * of the object type it is referring to. * @param t the traversal * @param assign the assign node * (<code>assign.isAssign()</code> is an implicit invariant) */ private void visitAssign(NodeTraversal t, Node assign) { JSDocInfo info = assign.getJSDocInfo(); Node lvalue = assign.getFirstChild(); Node rvalue = assign.getLastChild(); // Check property sets to 'object.property' when 'object' is known. if (lvalue.isGetProp()) { Node object = lvalue.getFirstChild(); JSType objectJsType = getJSType(object); Node property = lvalue.getLastChild(); String pname = property.getString(); // the first name in this getprop refers to an interface // we perform checks in addition to the ones below if (object.isGetProp()) { JSType jsType = getJSType(object.getFirstChild()); if (jsType.isInterface() && object.getLastChild().getString().equals("prototype")) { visitInterfaceGetprop(t, assign, object, pname, lvalue, rvalue); } } checkEnumAlias(t, info, rvalue); checkPropCreation(t, lvalue); // Prototype assignments are special, because they actually affect // the definition of a class. These are mostly validated // during TypedScopeCreator, and we only look for the "dumb" cases here. // object.prototype = ...; if (pname.equals("prototype")) { if (objectJsType != null && objectJsType.isFunctionType()) { FunctionType functionType = objectJsType.toMaybeFunctionType(); if (functionType.isConstructor()) { JSType rvalueType = rvalue.getJSType(); validator.expectObject(t, rvalue, rvalueType, OVERRIDING_PROTOTYPE_WITH_NON_OBJECT); // Only assign structs to the prototype of a @struct constructor if (functionType.makesStructs() && !rvalueType.isStruct()) { String funName = functionType.getTypeOfThis().toString(); compiler.report(t.makeError(assign,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> CONFLICTING_SHAPE_TYPE, "struct", funName)); } return; } } } // The generic checks for 'object.property' when 'object' is known, // and 'property' is declared on it. // object.property = ...; ObjectType type = ObjectType.cast( objectJsType.restrictByNotNullOrUndefined()); if (type != null) { if (type.hasProperty(pname) && !type.isPropertyTypeInferred(pname) && !propertyIsImplicitCast(type, pname)) { JSType expectedType = type.getPropertyType(pname); if (!expectedType.isUnknownType()) { validator.expectCanAssignToPropertyOf( t, assign, getJSType(rvalue), expectedType, object, pname); checkPropertyInheritanceOnGetpropAssign( t, assign, object, pname, info, expectedType); return; } } } // If we couldn't get the property type with normal object property // lookups, then check inheritance anyway with the unknown type. checkPropertyInheritanceOnGetpropAssign( t, assign, object, pname, info, getNativeType(UNKNOWN_TYPE)); } // Check qualified name sets to 'object' and 'object.property'. // This can sometimes handle cases when the type of 'object' is not known. // e.g., // var obj = createUnknownType(); // /** @type {number} */ obj.foo = true; JSType leftType = getJSType(lvalue); if (lvalue.isQualifiedName()) { // variable with inferred type case Var var = t.getScope().getVar(lvalue.getQualifiedName()); if (var != null) { if (var.isTypeInferred()) { return; } if (NodeUtil.getRootOfQualifiedName(lvalue).isThis() && t.getScope() != var.getScope()) { // Don't look at "this.foo" variables from other scopes. return; } if (var.getType() != null) { leftType = var.getType(); } } } // Fall through case for arbitrary LHS and arbitrary RHS. Node rightChild = assign.getLastChild(); JSType rightType = getJSType(rightChild); if (validator.expectCanAssignTo( t, assign, rightType, leftType, "assignment")) { ensureTyped(t, assign, rightType); } else { ensureTyped(t, assign); } } /** * After a struct object is created, we can't add new properties to it, with * one exception. We allow creation of "static" properties like * Foo.prototype.bar = baz; * where Foo.prototype is a struct, if the assignment happens at the top level * and the constructor Foo is defined in the same file. */ private void checkPropCreation(NodeTraversal t, Node lvalue) { if (lvalue.isGetProp()) { Node obj = lvalue.getFirstChild(); Node prop = lvalue.getLastChild(); JSType objType = getJSType(obj); String pname = prop.getString(); if (!objType.isStruct()

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> || objType.hasProperty(pname)) { return; } Scope s = t.getScope(); if (obj.isThis() && getJSType(s.getRootNode()).isConstructor()) { return; } // Prop created outside ctor, check that it's a static prop Node assgnExp = lvalue.getParent(); Node assgnStm = assgnExp.getParent(); if (objType instanceof ObjectType && s.isGlobal() && NodeUtil.isPrototypePropertyDeclaration(assgnStm)) { ObjectType instance = objType.toObjectType().getOwnerFunction().getInstanceType(); String file = lvalue.getSourceFileName(); Node ctor = instance.getConstructor().getSource(); if (ctor != null && ctor.getSourceFileName().equals(file)) { JSType rvalueType = assgnExp.getLastChild().getJSType(); instance.defineInferredProperty(pname, rvalueType, lvalue); return; } } report(t, prop, ILLEGAL_PROPERTY_CREATION); } } private void checkPropertyInheritanceOnGetpropAssign( NodeTraversal t, Node assign, Node object, String property, JSDocInfo info, JSType propertyType) { // Inheritance checks for prototype properties. // // TODO(nicksantos): This isn't the right place to do this check. We // really want to do this when we're looking at the constructor. // We'd find all its properties and make sure they followed inheritance // rules, like we currently do for @implements to make sure // all the methods are implemented. // // As-is, this misses many other ways to override a property. // // object.prototype.property = ...; if (object.isGetProp()) { Node object2 = object.getFirstChild(); String property2 = NodeUtil.getStringValue(object.getLastChild()); if ("prototype".equals(property2)) { JSType jsType = getJSType(object2); if (jsType.isFunctionType()) { FunctionType functionType = jsType.toMaybeFunctionType(); if (functionType.isConstructor() || functionType.isInterface()) { checkDeclaredPropertyInheritance( t, assign, functionType, property, info, propertyType); } } } } } /** * Visits an object literal field definition <code>key : value</code>. * * If the <code>lvalue</code> is a prototype modification, we change the * schema of the object type it is referring to. * * @param t the traversal * @param key the assign node */ private void visitObjLitKey( NodeTraversal t, Node key, Node objlit, JSType litType) { // Do not validate object lit value types in externs. We don't really care, // and it makes it easier to generate externs. if (objlit.isFromExterns()) { ensureTyped(t, key); return; } // Structs must have unquoted keys and dicts must have quoted keys if (litType.isStruct() && key.isQuotedString()) { report(t, key, ILLEGAL_OBJLIT_KEY, "

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>SuperClassConstructor(); boolean superClassHasProperty = superClass != null && superClass.getInstanceType().hasProperty(propertyName); boolean superClassHasDeclaredProperty = superClass != null && superClass.getInstanceType().isPropertyTypeDeclared(propertyName); // For interface boolean superInterfaceHasProperty = false; boolean superInterfaceHasDeclaredProperty = false; if (ctorType.isInterface()) { for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) { superInterfaceHasProperty = superInterfaceHasProperty || interfaceType.hasProperty(propertyName); superInterfaceHasDeclaredProperty = superInterfaceHasDeclaredProperty || interfaceType.isPropertyTypeDeclared(propertyName); } } boolean declaredOverride = info != null && info.isOverride(); boolean foundInterfaceProperty = false; if (ctorType.isConstructor()) { for (JSType implementedInterface : ctorType.getAllImplementedInterfaces()) { if (implementedInterface.isUnknownType() || implementedInterface.isEmptyType()) { continue; } FunctionType interfaceType = implementedInterface.toObjectType().getConstructor(); Preconditions.checkNotNull(interfaceType); boolean interfaceHasProperty = interfaceType.getPrototype().hasProperty(propertyName); foundInterfaceProperty = foundInterfaceProperty || interfaceHasProperty; if (reportMissingOverride.isOn() && !declaredOverride && interfaceHasProperty) { // @override not present, but the property does override an interface // property compiler.report(t.makeError(n, reportMissingOverride, HIDDEN_INTERFACE_PROPERTY, propertyName, interfaceType.getTopMostDefiningType(propertyName).toString())); } } } if (!declaredOverride && !superClassHasProperty && !superInterfaceHasProperty) { // nothing to do here, it's just a plain new property return; } ObjectType topInstanceType = superClassHasDeclaredProperty ? superClass.getTopMostDefiningType(propertyName) : null; boolean declaredLocally = ctorType.isConstructor() && (ctorType.getPrototype().hasOwnProperty(propertyName) || ctorType.getInstanceType().hasOwnProperty(propertyName)); if (reportMissingOverride.isOn() && !declaredOverride && superClassHasDeclaredProperty && declaredLocally) { // @override not present, but the property does override a superclass // property compiler.report(t.makeError(n, reportMissingOverride, HIDDEN_SUPERCLASS_PROPERTY, propertyName, topInstanceType.toString())); } // @override is present and we have to check that it is ok if (superClassHasDeclaredProperty) { // there is a superclass implementation JSType superClassPropType = superClass.getInstanceType().getPropertyType(propertyName); TemplateTypeMap ctorTypeMap = ctorType.getTypeOfThis().getTemplateTypeMap(); if (!ctorTypeMap.isEmpty()) { superClassPropType = superClassPropType.visit( new TemplateTypeMapReplacer(typeRegistry, ctorTypeMap)); } if (!propertyType.isSubtype(superClassPropType)) { compiler.report( t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH, propertyName, topInstanceType.toString(), superClassPropType.toString(), propertyType.toString())); } } else if (superInterfaceHasDeclaredProperty) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> // there is an super interface property for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) { if (interfaceType.hasProperty(propertyName)) { JSType superPropertyType = interfaceType.getPropertyType(propertyName); if (!propertyType.isSubtype(superPropertyType)) { topInstanceType = interfaceType.getConstructor(). getTopMostDefiningType(propertyName); compiler.report( t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH, propertyName, topInstanceType.toString(), superPropertyType.toString(), propertyType.toString())); } } } } else if (!foundInterfaceProperty && !superClassHasProperty && !superInterfaceHasProperty) { // there is no superclass nor interface implementation compiler.report( t.makeError(n, UNKNOWN_OVERRIDE, propertyName, ctorType.getInstanceType().toString())); } } /** * Given a constructor or an interface type, find out whether the unknown * type is a supertype of the current type. */ private static boolean hasUnknownOrEmptySupertype(FunctionType ctor) { Preconditions.checkArgument(ctor.isConstructor() || ctor.isInterface()); Preconditions.checkArgument(!ctor.isUnknownType()); // The type system should notice inheritance cycles on its own // and break the cycle. while (true) { ObjectType maybeSuperInstanceType = ctor.getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return false; } if (maybeSuperInstanceType.isUnknownType() || maybeSuperInstanceType.isEmptyType()) { return true; } ctor = maybeSuperInstanceType.getConstructor(); if (ctor == null) { return false; } Preconditions.checkState(ctor.isConstructor() || ctor.isInterface()); } } /** * Visits an ASSIGN node for cases such as * <pre> * interface.property2.property = ...; * </pre> */ private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object, String property, Node lvalue, Node rvalue) { JSType rvalueType = getJSType(rvalue); // Only 2 values are allowed for methods: // goog.abstractMethod // function () {}; // or for properties, no assignment such as: // InterfaceFoo.prototype.foobar; String abstractMethodName = compiler.getCodingConvention().getAbstractMethodName(); if (!rvalueType.isFunctionType()) { // This is bad i18n style but we don't localize our compiler errors. String abstractMethodMessage = (abstractMethodName != null) ? ", or " + abstractMethodName : ""; compiler.report( t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION, abstractMethodMessage)); } if (assign.getLastChild().isFunction() && !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) { compiler.report( t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY, abstractMethodName)); } } /** * Visits a NAME node. * * @param t The node traversal object that supplies context, such as the * scope

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. * @return whether the node is typeable or not */ boolean visitName(NodeTraversal t, Node n, Node parent) { // At this stage, we need to determine whether this is a leaf // node in an expression (which therefore needs to have a type // assigned for it) versus some other decorative node that we // can safely ignore. Function names, arguments (children of LP nodes) and // variable declarations are ignored. // TODO(user): remove this short-circuiting in favor of a // pre order traversal of the FUNCTION, CATCH, LP and VAR nodes. int parentNodeType = parent.getType(); if (parentNodeType == Token.FUNCTION || parentNodeType == Token.CATCH || parentNodeType == Token.PARAM_LIST || parentNodeType == Token.VAR) { return false; } JSType type = n.getJSType(); if (type == null) { type = getNativeType(UNKNOWN_TYPE); Var var = t.getScope().getVar(n.getString()); if (var != null) { JSType varType = var.getType(); if (varType != null) { type = varType; } } } ensureTyped(t, n, type); return true; } /** * Visits a GETPROP node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of <code>n</code> */ private void visitGetProp(NodeTraversal t, Node n, Node parent) { // obj.prop or obj.method() // Lots of types can appear on the left, a call to a void function can // never be on the left. getPropertyType will decide what is acceptable // and what isn't. Node property = n.getLastChild(); Node objNode = n.getFirstChild(); JSType childType = getJSType(objNode); if (childType.isDict()) { report(t, property, TypeValidator.ILLEGAL_PROPERTY_ACCESS, "'.'", "dict"); } else if (validator.expectNotNullOrUndefined(t, n, childType, "No properties on this expression", getNativeType(OBJECT_TYPE))) { checkPropertyAccess(childType, property.getString(), t, n); } ensureTyped(t, n); } /** * Emit a warning if we can prove that a property cannot possibly be * defined on an object. Note the difference between JS and a strictly * statically typed language: we're checking if the property * *cannot be defined*, whereas a java compiler would check if the * property *can be undefined*. */ private void checkPropertyAccess(JSType childType, String propName, NodeTraversal t, Node n) { // If the property type is unknown, check the object type to see if it // can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for // properties where we

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> if (pair.distance == shortest) { if (bestSoFar != null && pair.suggestion.compareToIgnoreCase(bestSoFar) > 0) { continue; } } shortest = pair.distance; bestSoFar = pair.suggestion; } } } } if (bestSoFar != null) { return new SuggestionPair(bestSoFar, shortest); } return null; } /** * Determines whether this node is testing for the existence of a property. * If true, we will not emit warnings about a missing property. * * @param getProp The GETPROP being tested. */ private boolean isPropertyTest(Node getProp) { Node parent = getProp.getParent(); switch (parent.getType()) { case Token.CALL: return parent.getFirstChild() != getProp && compiler.getCodingConvention().isPropertyTestFunction(parent); case Token.IF: case Token.WHILE: case Token.DO: case Token.FOR: return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; case Token.NOT: return parent.getParent().isOr() && parent.getParent().getFirstChild() == parent; case Token.CAST: return isPropertyTest(parent); } return false; } /** * Visits a GETELEM node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitGetElem(NodeTraversal t, Node n) { validator.expectIndexMatch( t, n, getJSType(n.getFirstChild()), getJSType(n.getLastChild())); ensureTyped(t, n); } /** * Visits a VAR node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitVar(NodeTraversal t, Node n) { // TODO(nicksantos): Fix this so that the doc info always shows up // on the NAME node. We probably want to wait for the parser // merge to fix this. JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null; for (Node name : n.children()) { Node value = name.getFirstChild(); // A null var would indicate a bug in the scope creation logic. Var var = t.getScope().getVar(name.getString()); if (value != null) { JSType valueType = getJSType(value); JSType nameType = var.getType(); nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType; JSDocInfo info = name.getJSDocInfo(); if (info == null) { info = varInfo; }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> checkEnumAlias(t, info, value); if (var.isTypeInferred()) { ensureTyped(t, name, valueType); } else { validator.expectCanAssignTo( t, value, valueType, nameType, "initializing variable"); } } } } /** * Visits a NEW node. */ private void visitNew(NodeTraversal t, Node n) { Node constructor = n.getFirstChild(); JSType type = getJSType(constructor).restrictByNotNullOrUndefined(); if (type.isConstructor() || type.isEmptyType() || type.isUnknownType()) { FunctionType fnType = type.toMaybeFunctionType(); if (fnType != null && fnType.hasInstanceType()) { visitParameterList(t, n, fnType); ensureTyped(t, n, fnType.getInstanceType()); } else { ensureTyped(t, n); } } else { report(t, n, NOT_A_CONSTRUCTOR); ensureTyped(t, n); } } /** * Check whether there's any property conflict for for a particular super * interface * @param t The node traversal object that supplies context * @param n The node being visited * @param functionName The function name being checked * @param properties The property names in the super interfaces that have * been visited * @param currentProperties The property names in the super interface * that have been visited * @param interfaceType The super interface that is being visited */ private void checkInterfaceConflictProperties(NodeTraversal t, Node n, String functionName, HashMap<String, ObjectType> properties, HashMap<String, ObjectType> currentProperties, ObjectType interfaceType) { ObjectType implicitProto = interfaceType.getImplicitPrototype(); Set<String> currentPropertyNames; if (implicitProto == null) { // This can be the case if interfaceType is proxy to a non-existent // object (which is a bad type annotation, but shouldn't crash). currentPropertyNames = ImmutableSet.of(); } else { currentPropertyNames = implicitProto.getOwnPropertyNames(); } for (String name : currentPropertyNames) { ObjectType oType = properties.get(name); if (oType != null) { if (!interfaceType.getPropertyType(name).isEquivalentTo( oType.getPropertyType(name))) { compiler.report( t.makeError(n, INCOMPATIBLE_EXTENDED_PROPERTY_TYPE, functionName, name, oType.toString(), interfaceType.toString())); } } currentProperties.put(name, interfaceType); } for (ObjectType iType : interfaceType.getCtorExtendedInterfaces()) { checkInterfaceConflictProperties(t, n, functionName, properties, currentProperties, iType); } } /** * Visits a {@link Token#FUNCTION} node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitFunction(NodeTraversal t, Node n) { FunctionType functionType = JSType.toMaybeFunctionType(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>n.getJSType()); String functionPrivateName = n.getFirstChild().getString(); if (functionType.isConstructor()) { FunctionType baseConstructor = functionType.getSuperClassConstructor(); if (baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) && baseConstructor != null && baseConstructor.isInterface()) { compiler.report( t.makeError(n, CONFLICTING_EXTENDED_TYPE, "constructor", functionPrivateName)); } else { if (baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE)) { ObjectType proto = functionType.getPrototype(); if (functionType.makesStructs() && !proto.isStruct()) { compiler.report(t.makeError(n, CONFLICTING_SHAPE_TYPE, "struct", functionPrivateName)); } else if (functionType.makesDicts() && !proto.isDict()) { compiler.report(t.makeError(n, CONFLICTING_SHAPE_TYPE, "dict", functionPrivateName)); } } // All interfaces are properly implemented by a class for (JSType baseInterface : functionType.getImplementedInterfaces()) { boolean badImplementedType = false; ObjectType baseInterfaceObj = ObjectType.cast(baseInterface); if (baseInterfaceObj != null) { FunctionType interfaceConstructor = baseInterfaceObj.getConstructor(); if (interfaceConstructor != null && !interfaceConstructor.isInterface()) { badImplementedType = true; } } else { badImplementedType = true; } if (badImplementedType) { report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName); } } // check properties validator.expectAllInterfaceProperties(t, n, functionType); } } else if (functionType.isInterface()) { // Interface must extend only interfaces for (ObjectType extInterface : functionType.getExtendedInterfaces()) { if (extInterface.getConstructor() != null && !extInterface.getConstructor().isInterface()) { compiler.report( t.makeError(n, CONFLICTING_EXTENDED_TYPE, "interface", functionPrivateName)); } } // Check whether the extended interfaces have any conflicts if (functionType.getExtendedInterfacesCount() > 1) { // Only check when extending more than one interfaces HashMap<String, ObjectType> properties = new HashMap<String, ObjectType>(); HashMap<String, ObjectType> currentProperties = new HashMap<String, ObjectType>(); for (ObjectType interfaceType : functionType.getExtendedInterfaces()) { currentProperties.clear(); checkInterfaceConflictProperties(t, n, functionPrivateName, properties, currentProperties, interfaceType); properties.putAll(currentProperties); } } } } /** * Visits a CALL node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitCall(NodeTraversal t, Node n) { Node child = n.getFirstChild(); JSType childType = getJSType(child).restrictByNotNullOrUndefined(); if

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> (!childType.canBeCalled()) { report(t, n, NOT_CALLABLE, childType.toString()); ensureTyped(t, n); return; } // A couple of types can be called as if they were functions. // If it is a function type, then validate parameters. if (childType.isFunctionType()) { FunctionType functionType = childType.toMaybeFunctionType(); boolean isExtern = false; JSDocInfo functionJSDocInfo = functionType.getJSDocInfo(); if (functionJSDocInfo != null && functionJSDocInfo.getAssociatedNode() != null) { isExtern = functionJSDocInfo.getAssociatedNode().isFromExterns(); } // Non-native constructors should not be called directly // unless they specify a return type and are defined // in an extern. if (functionType.isConstructor() && !functionType.isNativeObjectType() && (functionType.getReturnType().isUnknownType() || functionType.getReturnType().isVoidType())) { report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString()); } // Functions with explicit 'this' types must be called in a GETPROP // or GETELEM. if (functionType.isOrdinaryFunction() && !functionType.getTypeOfThis().isUnknownType() && !(functionType.getTypeOfThis().toObjectType() != null && functionType.getTypeOfThis().toObjectType().isNativeObjectType()) && !(child.isGetElem() || child.isGetProp())) { report(t, n, EXPECTED_THIS_TYPE, functionType.toString()); } visitParameterList(t, n, functionType); ensureTyped(t, n, functionType.getReturnType()); } else { ensureTyped(t, n); } // TODO(nicksantos): Add something to check for calls of RegExp objects, // which is not supported by IE. Either say something about the return type // or warn about the non-portability of the call or both. } /** * Visits the parameters of a CALL or a NEW node. */ private void visitParameterList(NodeTraversal t, Node call, FunctionType functionType) { Iterator<Node> arguments = call.children().iterator(); arguments.next(); // skip the function name Iterator<Node> parameters = functionType.getParameters().iterator(); int ordinal = 0; Node parameter = null; Node argument = null; while (arguments.hasNext() && (parameters.hasNext() || parameter != null && parameter.isVarArgs())) { // If there are no parameters left in the list, then the while loop // above implies that this must be a var_args function. if (parameters.hasNext()) { parameter = parameters.next(); } argument = arguments.next(); ordinal++; validator.expectArgumentMatchesParameter(t, argument, getJSType(argument), getJSType(parameter), call, ordinal); } int numArgs = call.getChildCount() - 1; int minArgs = functionType.getMinArguments(); int maxArgs = functionType.getMaxArguments(); if (min

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Args > numArgs || maxArgs < numArgs) { report(t, call, WRONG_ARGUMENT_COUNT, validator.getReadableJSTypeName(call.getFirstChild(), false), String.valueOf(numArgs), String.valueOf(minArgs), maxArgs != Integer.MAX_VALUE ? " and no more than " + maxArgs + " argument(s)" : ""); } } /** * Visits a RETURN node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitReturn(NodeTraversal t, Node n) { JSType jsType = getJSType(t.getEnclosingFunction()); if (jsType.isFunctionType()) { FunctionType functionType = jsType.toMaybeFunctionType(); JSType returnType = functionType.getReturnType(); // if no return type is specified, undefined must be returned // (it's a void function) if (returnType == null) { returnType = getNativeType(VOID_TYPE); } // fetching the returned value's type Node valueNode = n.getFirstChild(); JSType actualReturnType; if (valueNode == null) { actualReturnType = getNativeType(VOID_TYPE); valueNode = n; } else { actualReturnType = getJSType(valueNode); } // verifying validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType, "inconsistent return type"); } } /** * This function unifies the type checking involved in the core binary * operators and the corresponding assignment operators. The representation * used internally is such that common code can handle both kinds of * operators easily. * * @param op The operator. * @param t The traversal object, needed to report errors. * @param n The node being checked. */ private void visitBinaryOperator(int op, NodeTraversal t, Node n) { Node left = n.getFirstChild(); JSType leftType = getJSType(left); Node right = n.getLastChild(); JSType rightType = getJSType(right); switch (op) { case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.LSH: case Token.RSH: case Token.ASSIGN_URSH: case Token.URSH: if (!leftType.matchesInt32Context()) { report(t, left, BIT_OPERATION, NodeUtil.opToStr(n.getType()), leftType.toString()); } if (!rightType.matchesUint32Context()) { report(t, right, BIT_OPERATION, NodeUtil.opToStr(n.getType()), rightType.toString()); } break; case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_SUB: case Token.DIV: case Token.MOD: case Token.MUL: case Token.SUB: validator.expectNumber(t, left, leftType, "left operand"); validator.expectNumber(t, right,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; /** * An unresolved type that was forward declared. So we know it exists, * but that it wasn't pulled into this binary. * * In most cases, it behaves like a bottom type in the type lattice: * no real type should be assigned to a NoResolvedType, but the * NoResolvedType is a subtype of everything. In a few cases, it behaves * like the unknown type: properties of this type are also NoResolved types, * and comparisons to other types always have an unknown result. * * @author nicksantos@google.com (Nick Santos) */ class NoResolvedType extends NoType { private static final long serialVersionUID = 1L; NoResolvedType(JSTypeRegistry registry) { super(registry); } @Override public boolean isNoResolvedType() { return true; } @Override public boolean isNoType() { return false; } @Override public boolean isConstructor() { return false; } @Override public boolean isSubtype(JSType that) { if (JSType.isSubtypeHelper(this, that)) { return true; } else { return !that.isNoType(); } } @Override String toStringHelper(boolean forAnnotations) { return forAnnotations ? "?" : "NoResolvedType"; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.ImmutableSet; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.jscomp.regex.RegExpTree; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Set; /** * Look for references to the global RegExp object that would cause * regular expressions to be unoptimizable, and checks that regular expressions * are syntactically valid. * * @author johnlenz@google.com (John Lenz) */ class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType REGEXP_REFERENCE = DiagnosticType.warning("JSC_REGEXP_REFERENCE", "References to the global RegExp object prevents " + "optimization of regular expressions."); static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning( "JSC_MALFORMED_REGEXP", "Malformed Regular Expression: {0}"); private static final Set<String> REGEXP_PROPERTY_BLACKLIST = ImmutableSet.of( "$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9", "$_", "$input", // The following would also be blacklisted, but they aren't valid // identifiers, so can't be accessed with the '.' operator anyway. // "$*", "$&", "$+", "$`", "$'", "input", "lastMatch", "lastParen", "leftContext", "rightContext", "global", "ignoreCase", "lastIndex", "multiline", "source"); private final AbstractCompiler compiler; private boolean globalRegExpPropertiesUsed = false; public boolean isGlobalRegExpPropertiesUsed() { return globalRegExpPropertiesUsed; } public CheckRegExp(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isReferenceName(n)) { String name = n.getString(); if (name.equals("RegExp") && t.getScope().getVar(name) == null) { int parentType = parent.getType(); boolean first = (n == parent.getFirstChild()); if (!((parentType == Token.NEW && first) || (parentType == Token.CALL &&

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> private JSDocInfo handleJsDoc(AstNode node, Node irNode) { Comment comment = node.getJsDocNode(); if (comment != null) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode); parsedComments.add(comment); if (!handlePossibleFileOverviewJsDoc(jsDocParser)) { JSDocInfo info = jsDocParser.retrieveAndResetParsedJSDocInfo(); if (info != null) { validateTypeAnnotations(info, node); } return info; } } return null; } private void validateTypeAnnotations(JSDocInfo info, AstNode node) { if (info.hasType()) { boolean valid = false; switch (node.getType()) { // Casts are valid case com.google.javascript.rhino.head.Token.LP: valid = node instanceof ParenthesizedExpression; break; // Variable declarations are valid case com.google.javascript.rhino.head.Token.VAR: valid = true; break; // Function declarations are valid case com.google.javascript.rhino.head.Token.FUNCTION: FunctionNode fnNode = (FunctionNode) node; valid = fnNode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT; break; // Object literal properties, catch declarations and variable // initializers are valid. case com.google.javascript.rhino.head.Token.NAME: AstNode parent = node.getParent(); valid = parent instanceof ObjectProperty || parent instanceof CatchClause || parent instanceof FunctionNode || (parent instanceof VariableInitializer && node == ((VariableInitializer) parent).getTarget()); break; // Object literal properties are valid case com.google.javascript.rhino.head.Token.GET: case com.google.javascript.rhino.head.Token.SET: case com.google.javascript.rhino.head.Token.NUMBER: case com.google.javascript.rhino.head.Token.STRING: valid = node.getParent() instanceof ObjectProperty; break; // Property assignments are valid, if at the root of an expression. case com.google.javascript.rhino.head.Token.ASSIGN: if (node instanceof Assignment) { valid = isExprStmt(node.getParent()) && isPropAccess(((Assignment) node).getLeft()); } break; // Property definitions are valid, if at the root of an expression. case com.google.javascript.rhino.head.Token.GETPROP: case com.google.javascript.rhino.head.Token.GETELEM: valid = isExprStmt(node.getParent()); break; case com.google.javascript.rhino.head.Token.CALL: valid = info.isDefine(); break; } if (!valid) { errorReporter.warning(MISPLACED_TYPE_ANNOTATION, sourceName, node.getLineno(), "", 0); } } } private boolean isPropAccess(AstNode node) { return node.getType() == com.google.javascript.rhino.head.Token.GETPROP || node.getType() == com.google.javascript

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.rhino.head.Token.GETELEM; } private boolean isExprStmt(AstNode node) { return node.getType() == com.google.javascript.rhino.head.Token.EXPR_RESULT || node.getType() == com.google.javascript.rhino.head.Token.EXPR_VOID; } private Node transform(AstNode node) { Node irNode = justTransform(node); JSDocInfo jsDocInfo = handleJsDoc(node, irNode); if (jsDocInfo != null) { irNode = maybeInjectCastNode(node, jsDocInfo, irNode); irNode.setJSDocInfo(jsDocInfo); } setSourceInfo(irNode, node); return irNode; } private Node maybeInjectCastNode(AstNode node, JSDocInfo info, Node irNode) { if (node.getType() == com.google.javascript.rhino.head.Token.LP && node instanceof ParenthesizedExpression && info.hasType()) { irNode = newNode(Token.CAST, irNode); } return irNode; } /** * Parameter NAMEs are special, because they can have inline type docs * attached. * * function f(/** string &#42;/ x) {} * annotates 'x' as a string. * * @see <a href="http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs"> * Using Inline Doc Comments</a> */ private Node transformParameter(AstNode node) { Node irNode = justTransform(node); Comment comment = node.getJsDocNode(); if (comment != null) { JSDocInfo info = parseInlineTypeDoc(comment, irNode); if (info != null) { irNode.setJSDocInfo(info); } } setSourceInfo(irNode, node); return irNode; } private Node transformNameAsString(Name node) { Node irNode = transformDispatcher.processName(node, true); JSDocInfo jsDocInfo = handleJsDoc(node, irNode); if (jsDocInfo != null) { irNode.setJSDocInfo(jsDocInfo); } setSourceInfo(irNode, node); return irNode; } private Node transformNumberAsString(NumberLiteral literalNode) { Node irNode = newStringNode(getStringValue(literalNode.getNumber())); JSDocInfo jsDocInfo = handleJsDoc(literalNode, irNode); if (jsDocInfo != null) { irNode.setJSDocInfo(jsDocInfo); } setSourceInfo(irNode, literalNode); return irNode; } private static String getStringValue(double value) { long longValue = (long) value; // Return "1" instead of "1.0" if (longValue == value) { return Long.toString(longValue); } else { return Double.toString(value); } } private void setSourceInfo(Node irNode, AstNode node) { if (irNode.getLineno() == -1) { // If

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> we didn't already set the line, then set it now. This avoids // cases like ParenthesizedExpression where we just return a previous // node, but don't want the new node to get its parent's line number. int lineno = node.getLineno(); irNode.setLineno(lineno); int charno = position2charno(node.getAbsolutePosition()); irNode.setCharno(charno); maybeSetLengthFrom(irNode, node); } } /** * Creates a JsDocInfoParser and parses the JsDoc string. * * Used both for handling individual JSDoc comments and for handling * file-level JSDoc comments (@fileoverview and @license). * * @param node The JsDoc Comment node to parse. * @param irNode * @return A JsDocInfoParser. Will contain either fileoverview JsDoc, or * normal JsDoc, or no JsDoc (if the method parses to the wrong level). */ private JsDocInfoParser createJsDocInfoParser(Comment node, Node irNode) { String comment = node.getValue(); int lineno = node.getLineno(); int position = node.getAbsolutePosition(); // The JsDocInfoParser expects the comment without the initial '/**'. int numOpeningChars = 3; JsDocInfoParser jsdocParser = new JsDocInfoParser( new JsDocTokenStream(comment.substring(numOpeningChars), lineno, position2charno(position) + numOpeningChars), node, irNode, config, errorReporter); jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder); jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo); jsdocParser.parse(); return jsdocParser; } /** * Parses inline type info. */ private JSDocInfo parseInlineTypeDoc(Comment node, Node irNode) { String comment = node.getValue(); int lineno = node.getLineno(); int position = node.getAbsolutePosition(); // The JsDocInfoParser expects the comment without the initial '/**'. int numOpeningChars = 3; JsDocInfoParser parser = new JsDocInfoParser( new JsDocTokenStream(comment.substring(numOpeningChars), lineno, position2charno(position) + numOpeningChars), node, irNode, config, errorReporter); return parser.parseInlineTypeDoc(); } // Set the length on the node if we're in IDE mode. private void maybeSetLengthFrom(Node node, AstNode source) { if (config.isIdeMode) { node.setLength(source.getLength()); } } private int position2charno(int position) { int lineIndex = sourceString.lastIndexOf('\n', position); if (lineIndex == -1) { return position; } else { // Subtract one for initial position being 0. return position - lineIndex - 1; } } private Node justTransform(AstNode node) { return transformDispatcher.process(node); } private class TransformDispatcher extends TypeSafeDispatcher<Node> { private Node processGeneric(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> com.google.javascript.rhino.head.Node n) { Node node = newNode(transformTokenType(n.getType())); for (com.google.javascript.rhino.head.Node child : n) { node.addChildToBack(transform((AstNode) child)); } return node; } /** * Transforms the given node and then sets its type to Token.STRING if it * was Token.NAME. If its type was already Token.STRING, then quotes it. * Used for properties, as the old AST uses String tokens, while the new one * uses Name tokens for unquoted strings. For example, in * var o = {'a' : 1, b: 2}; * the string 'a' is quoted, while the name b is turned into a string, but * unquoted. */ private Node transformAsString(AstNode n) { Node ret; if (n instanceof Name) { ret = transformNameAsString((Name) n); } else if (n instanceof NumberLiteral) { ret = transformNumberAsString((NumberLiteral) n); ret.putBooleanProp(Node.QUOTED_PROP, true); } else { ret = transform(n); ret.putBooleanProp(Node.QUOTED_PROP, true); } Preconditions.checkState(ret.isString()); return ret; } @Override Node processArrayLiteral(ArrayLiteral literalNode) { if (literalNode.isDestructuring()) { reportDestructuringAssign(literalNode); } Node node = newNode(Token.ARRAYLIT); for (AstNode child : literalNode.getElements()) { Node c = transform(child); node.addChildToBack(c); } return node; } @Override Node processAssignment(Assignment assignmentNode) { Node assign = processInfixExpression(assignmentNode); Node target = assign.getFirstChild(); if (!validAssignmentTarget(target)) { errorReporter.error( "invalid assignment target", sourceName, target.getLineno(), "", 0); } return assign; } @Override Node processAstRoot(AstRoot rootNode) { Node node = newNode(Token.SCRIPT); for (com.google.javascript.rhino.head.Node child : rootNode) { node.addChildToBack(transform((AstNode) child)); } parseDirectives(node); return node; } /** * Parse the directives, encode them in the AST, and remove their nodes. * * For information on ES5 directives, see section 14.1 of * ECMA-262, Edition 5. * * It would be nice if Rhino would eventually take care of this for * us, but right now their directive-processing is a one-off. */ private void parseDirectives(Node node) { // Remove all the directives, and encode them in the AST. Set<String> directives = null; while (isDirective(node.getFirstChild())) { String directive = node.removeFirstChild().getFirstChild().getString(); if (directives == null) { directives = Sets.newHashSet(directive); } else { directives.add

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(directive); } } if (directives != null) { node.setDirectives(directives); } } private boolean isDirective(Node n) { if (n == null) { return false; } int nType = n.getType(); return nType == Token.EXPR_RESULT && n.getFirstChild().isString() && ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString()); } @Override Node processBlock(Block blockNode) { return processGeneric(blockNode); } @Override Node processBreakStatement(BreakStatement statementNode) { Node node = newNode(Token.BREAK); if (statementNode.getBreakLabel() != null) { Node labelName = transform(statementNode.getBreakLabel()); // Change the NAME to LABEL_NAME labelName.setType(Token.LABEL_NAME); node.addChildToBack(labelName); } return node; } @Override Node processCatchClause(CatchClause clauseNode) { AstNode catchVar = clauseNode.getVarName(); Node node = newNode(Token.CATCH, transform(catchVar)); if (clauseNode.getCatchCondition() != null) { errorReporter.error( "Catch clauses are not supported", sourceName, clauseNode.getCatchCondition().getLineno(), "", 0); } node.addChildToBack(transformBlock(clauseNode.getBody())); return node; } @Override Node processConditionalExpression(ConditionalExpression exprNode) { return newNode( Token.HOOK, transform(exprNode.getTestExpression()), transform(exprNode.getTrueExpression()), transform(exprNode.getFalseExpression())); } @Override Node processContinueStatement(ContinueStatement statementNode) { Node node = newNode(Token.CONTINUE); if (statementNode.getLabel() != null) { Node labelName = transform(statementNode.getLabel()); // Change the NAME to LABEL_NAME labelName.setType(Token.LABEL_NAME); node.addChildToBack(labelName); } return node; } @Override Node processDoLoop(DoLoop loopNode) { return newNode( Token.DO, transformBlock(loopNode.getBody()), transform(loopNode.getCondition())); } @Override Node processElementGet(ElementGet getNode) { return newNode( Token.GETELEM, transform(getNode.getTarget()), transform(getNode.getElement())); } @Override Node processEmptyExpression(EmptyExpression exprNode) { Node node = newNode(Token.EMPTY); return node; } @Override Node processEmptyStatement(EmptyStatement exprNode) { Node node = newNode(Token.EMPTY); return node; } @Override Node processExpressionStatement(ExpressionStatement statementNode) { Node node = newNode(transformTokenType(statementNode.getType())); node.addChildToBack(transform(statementNode.getExpression())); return node; } @Override Node processForInLoop(ForInLoop loopNode) { if (loopNode.isForEach()) { errorReporter.error( "unsupported language extension: for each", sourceName, loopNode.getLineno(), "", 0

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>); // Return the bare minimum to put the AST in a valid state. return newNode(Token.EXPR_RESULT, Node.newNumber(0)); } return newNode( Token.FOR, transform(loopNode.getIterator()), transform(loopNode.getIteratedObject()), transformBlock(loopNode.getBody())); } @Override Node processForLoop(ForLoop loopNode) { Node node = newNode( Token.FOR, transform(loopNode.getInitializer()), transform(loopNode.getCondition()), transform(loopNode.getIncrement())); node.addChildToBack(transformBlock(loopNode.getBody())); return node; } @Override Node processFunctionCall(FunctionCall callNode) { Node node = newNode(transformTokenType(callNode.getType()), transform(callNode.getTarget())); for (AstNode child : callNode.getArguments()) { node.addChildToBack(transform(child)); } node.setLineno(node.getFirstChild().getLineno()); node.setCharno(node.getFirstChild().getCharno()); maybeSetLengthFrom(node, callNode); return node; } @Override Node processFunctionNode(FunctionNode functionNode) { Name name = functionNode.getFunctionName(); Boolean isUnnamedFunction = false; if (name == null) { int functionType = functionNode.getFunctionType(); if (functionType != FunctionNode.FUNCTION_EXPRESSION) { errorReporter.error( "unnamed function statement", sourceName, functionNode.getLineno(), "", 0); // Return the bare minimum to put the AST in a valid state. return newNode(Token.EXPR_RESULT, Node.newNumber(0)); } name = new Name(); name.setIdentifier(""); isUnnamedFunction = true; } Node node = newNode(Token.FUNCTION); Node newName = transform(name); if (isUnnamedFunction) { // Old Rhino tagged the empty name node with the line number of the // declaration. newName.setLineno(functionNode.getLineno()); // TODO(bowdidge) Mark line number of paren correctly. // Same problem as below - the left paren might not be on the // same line as the function keyword. int lpColumn = functionNode.getAbsolutePosition() + functionNode.getLp(); newName.setCharno(position2charno(lpColumn)); maybeSetLengthFrom(newName, name); } node.addChildToBack(newName); Node lp = newNode(Token.PARAM_LIST); // The left paren's complicated because it's not represented by an // AstNode, so there's nothing that has the actual line number that it // appeared on. We know the paren has to appear on the same line as the // function name (or else a semicolon will be inserted.) If there's no // function name, assume the paren was on the same line as the function. // TODO(bowdidge): Mark line number of paren correctly. Name fnName = functionNode.getFunctionName(); if (fnName != null) { lp.setLineno(fnName.getLineno());

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> } else { lp.setLineno(functionNode.getLineno()); } int lparenCharno = functionNode.getLp() + functionNode.getAbsolutePosition(); lp.setCharno(position2charno(lparenCharno)); for (AstNode param : functionNode.getParams()) { Node paramNode = transformParameter(param); // When in ideMode Rhino can generate a param list with only a single // ErrorNode. This is transformed into an EMPTY node. Drop this node in // ideMode to keep the AST in a valid state. if (paramNode.isName()) { lp.addChildToBack(paramNode); } else { // We expect this in ideMode or when there is an error handling // destructuring parameter assignments which aren't supported // (an error has already been reported). Preconditions.checkState( config.isIdeMode || paramNode.isObjectLit() || paramNode.isArrayLit()); } } node.addChildToBack(lp); Node bodyNode = transform(functionNode.getBody()); if (!bodyNode.isBlock()) { // When in ideMode Rhino tries to parse some constructs the compiler // doesn't support, repair it here. see Rhino's // Parser#parseFunctionBodyExpr. Preconditions.checkState(config.isIdeMode); bodyNode = IR.block(); } parseDirectives(bodyNode); node.addChildToBack(bodyNode); return node; } @Override Node processIfStatement(IfStatement statementNode) { Node node = newNode(Token.IF); node.addChildToBack(transform(statementNode.getCondition())); node.addChildToBack(transformBlock(statementNode.getThenPart())); if (statementNode.getElsePart() != null) { node.addChildToBack(transformBlock(statementNode.getElsePart())); } return node; } @Override Node processInfixExpression(InfixExpression exprNode) { Node n = newNode( transformTokenType(exprNode.getType()), transform(exprNode.getLeft()), transform(exprNode.getRight())); n.setLineno(exprNode.getLineno()); n.setCharno(position2charno(exprNode.getAbsolutePosition())); maybeSetLengthFrom(n, exprNode); return n; } @Override Node processKeywordLiteral(KeywordLiteral literalNode) { return newNode(transformTokenType(literalNode.getType())); } @Override Node processLabel(Label labelNode) { return newStringNode(Token.LABEL_NAME, labelNode.getName()); } @Override Node processLabeledStatement(LabeledStatement statementNode) { Node node = newNode(Token.LABEL); Node prev = null; Node cur = node; for (Label label : statementNode.getLabels()) { if (prev != null) { prev.addChildToBack(cur); } cur.addChildToBack(transform(label)); cur.setLineno(label.getLineno()); maybeSetLengthFrom(cur, label); int clauseAbsolutePosition = position2charno(label.getAbsolutePosition()); cur.setCharno(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>clauseAbsolutePosition); prev = cur; cur = newNode(Token.LABEL); } prev.addChildToBack(transform(statementNode.getStatement())); return node; } @Override Node processName(Name nameNode) { return processName(nameNode, false); } Node processName(Name nameNode, boolean asString) { if (asString) { return newStringNode(Token.STRING, nameNode.getIdentifier()); } else { if (isReservedKeyword(nameNode.getIdentifier())) { errorReporter.error( "identifier is a reserved word", sourceName, nameNode.getLineno(), "", 0); } return newStringNode(Token.NAME, nameNode.getIdentifier()); } } private boolean isAllowedProp(String identifier) { if (config.languageMode == LanguageMode.ECMASCRIPT3) { return !TokenStream.isKeyword(identifier); } return true; } private boolean isReservedKeyword(String identifier) { if (config.languageMode == LanguageMode.ECMASCRIPT3) { return TokenStream.isKeyword(identifier); } return reservedKeywords != null && reservedKeywords.contains(identifier); } @Override Node processNewExpression(NewExpression exprNode) { Node node = newNode( transformTokenType(exprNode.getType()), transform(exprNode.getTarget())); for (AstNode child : exprNode.getArguments()) { node.addChildToBack(transform(child)); } node.setLineno(exprNode.getLineno()); node.setCharno(position2charno(exprNode.getAbsolutePosition())); maybeSetLengthFrom(node, exprNode); return node; } @Override Node processNumberLiteral(NumberLiteral literalNode) { return newNumberNode(literalNode.getNumber()); } @Override Node processObjectLiteral(ObjectLiteral literalNode) { if (literalNode.isDestructuring()) { reportDestructuringAssign(literalNode); } Node node = newNode(Token.OBJECTLIT); for (ObjectProperty el : literalNode.getElements()) { if (config.languageMode == LanguageMode.ECMASCRIPT3) { if (el.isGetter()) { reportGetter(el); continue; } else if (el.isSetter()) { reportSetter(el); continue; } } AstNode rawKey = el.getLeft(); Node key = transformAsString(rawKey); key.setType(Token.STRING_KEY); if (rawKey instanceof Name && !isAllowedProp(key.getString())) { errorReporter.warning(INVALID_ES3_PROP_NAME, sourceName, key.getLineno(), "", key.getCharno()); } Node value = transform(el.getRight()); if (el.isGetter()) { key.setType(Token.GETTER_DEF); Preconditions.checkState(value.isFunction()); if (getFnParamNode(value).hasChildren()) { reportGetterParam(el.getLeft()); } } else if (el.isSetter()) { key.setType(Token.SETTER_DEF); Preconditions.checkState(value.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> later, and is not understood // on IE. So we need to preserve it as-is. This is really // obnoxious, because we do not have a good way to represent // how the original string was encoded without making the // representation of strings much more complicated. // // To handle this, we look at the original source test, and // mark the string as \v-encoded or not. If a string is // \v encoded, then all the vertical tabs in that string // will be encoded with a \v. int start = literalNode.getAbsolutePosition(); int end = start + literalNode.getLength(); if (start < sourceString.length() && (sourceString.substring( start, Math.min(sourceString.length(), end)) .indexOf("\\v") != -1)) { n.putBooleanProp(Node.SLASH_V, true); } } return n; } @Override Node processSwitchCase(SwitchCase caseNode) { Node node; if (caseNode.isDefault()) { node = newNode(Token.DEFAULT_CASE); } else { AstNode expr = caseNode.getExpression(); node = newNode(Token.CASE, transform(expr)); } Node block = newNode(Token.BLOCK); block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true); block.setLineno(caseNode.getLineno()); block.setCharno(position2charno(caseNode.getAbsolutePosition())); maybeSetLengthFrom(block, caseNode); if (caseNode.getStatements() != null) { for (AstNode child : caseNode.getStatements()) { block.addChildToBack(transform(child)); } } node.addChildToBack(block); return node; } @Override Node processSwitchStatement(SwitchStatement statementNode) { Node node = newNode(Token.SWITCH, transform(statementNode.getExpression())); for (AstNode child : statementNode.getCases()) { node.addChildToBack(transform(child)); } return node; } @Override Node processThrowStatement(ThrowStatement statementNode) { return newNode(Token.THROW, transform(statementNode.getExpression())); } @Override Node processTryStatement(TryStatement statementNode) { Node node = newNode(Token.TRY, transformBlock(statementNode.getTryBlock())); Node block = newNode(Token.BLOCK); boolean lineSet = false; for (CatchClause cc : statementNode.getCatchClauses()) { // Mark the enclosing block at the same line as the first catch // clause. if (lineSet == false) { block.setLineno(cc.getLineno()); maybeSetLengthFrom(block, cc); lineSet = true; } block.addChildToBack(transform(cc)); } node.addChildToBack(block); AstNode finallyBlock = statementNode.getFinallyBlock(); if (finallyBlock != null) { node.addChildToBack(transformBlock(finallyBlock)); } // If we didn't set the line on the catch clause, then // we've got an empty catch clause

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>. Set its line to be the same // as the finally block (to match Old Rhino's behavior.) if ((lineSet == false) && (finallyBlock != null)) { block.setLineno(finallyBlock.getLineno()); maybeSetLengthFrom(block, finallyBlock); } return node; } @Override Node processUnaryExpression(UnaryExpression exprNode) { int type = transformTokenType(exprNode.getType()); Node operand = transform(exprNode.getOperand()); if (type == Token.NEG && operand.isNumber()) { operand.setDouble(-operand.getDouble()); return operand; } else { if (type == Token.DELPROP && !(operand.isGetProp() || operand.isGetElem() || operand.isName())) { String msg = "Invalid delete operand. Only properties can be deleted."; errorReporter.error( msg, sourceName, operand.getLineno(), "", 0); } else if (type == Token.INC || type == Token.DEC) { if (!validAssignmentTarget(operand)) { String msg = (type == Token.INC) ? "invalid increment target" : "invalid decrement target"; errorReporter.error( msg, sourceName, operand.getLineno(), "", 0); } } Node node = newNode(type, operand); if (exprNode.isPostfix()) { node.putBooleanProp(Node.INCRDECR_PROP, true); } return node; } } private boolean validAssignmentTarget(Node target) { switch (target.getType()) { case Token.CAST: // CAST is a bit weird, but syntactically valid. case Token.NAME: case Token.GETPROP: case Token.GETELEM: return true; } return false; } @Override Node processVariableDeclaration(VariableDeclaration declarationNode) { if (!config.acceptConstKeyword && declarationNode.getType() == com.google.javascript.rhino.head.Token.CONST) { processIllegalToken(declarationNode); } Node node = newNode(Token.VAR); for (VariableInitializer child : declarationNode.getVariables()) { node.addChildToBack(transform(child)); } return node; } @Override Node processVariableInitializer(VariableInitializer initializerNode) { Node node = transform(initializerNode.getTarget()); if (initializerNode.getInitializer() != null) { Node initalizer = transform(initializerNode.getInitializer()); node.addChildToBack(initalizer); } return node; } @Override Node processWhileLoop(WhileLoop loopNode) { return newNode( Token.WHILE, transform(loopNode.getCondition()), transformBlock(loopNode.getBody())); } @Override Node processWithStatement(WithStatement statementNode) { return newNode( Token.WITH, transform(statementNode.getExpression()), transformBlock(statementNode.getStatement())); } @Override Node processIllegalToken(AstNode node) { errorReporter.error( "Unsupported syntax: " + com.google.javascript.rhino.head.Token.type

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>ToName( node.getType()), sourceName, node.getLineno(), "", 0); return newNode(Token.EMPTY); } void reportDestructuringAssign(AstNode node) { errorReporter.error( "destructuring assignment forbidden", sourceName, node.getLineno(), "", 0); } void reportGetter(AstNode node) { errorReporter.error( GETTER_ERROR_MESSAGE, sourceName, node.getLineno(), "", 0); } void reportSetter(AstNode node) { errorReporter.error( SETTER_ERROR_MESSAGE, sourceName, node.getLineno(), "", 0); } void reportGetterParam(AstNode node) { errorReporter.error( "getters may not have parameters", sourceName, node.getLineno(), "", 0); } void reportSetterParam(AstNode node) { errorReporter.error( "setters must have exactly one parameter", sourceName, node.getLineno(), "", 0); } } private static int transformTokenType(int token) { switch (token) { case com.google.javascript.rhino.head.Token.RETURN: return Token.RETURN; case com.google.javascript.rhino.head.Token.BITOR: return Token.BITOR; case com.google.javascript.rhino.head.Token.BITXOR: return Token.BITXOR; case com.google.javascript.rhino.head.Token.BITAND: return Token.BITAND; case com.google.javascript.rhino.head.Token.EQ: return Token.EQ; case com.google.javascript.rhino.head.Token.NE: return Token.NE; case com.google.javascript.rhino.head.Token.LT: return Token.LT; case com.google.javascript.rhino.head.Token.LE: return Token.LE; case com.google.javascript.rhino.head.Token.GT: return Token.GT; case com.google.javascript.rhino.head.Token.GE: return Token.GE; case com.google.javascript.rhino.head.Token.LSH: return Token.LSH; case com.google.javascript.rhino.head.Token.RSH: return Token.RSH; case com.google.javascript.rhino.head.Token.URSH: return Token.URSH; case com.google.javascript.rhino.head.Token.ADD: return Token.ADD; case com.google.javascript.rhino.head.Token.SUB: return Token.SUB; case com.google.javascript.rhino.head.Token.MUL: return Token.MUL; case com.google.javascript.rhino.head.Token.DIV: return Token.DIV; case com.google.javascript.rhino.head.Token.MOD: return Token.MOD; case com.google.javascript.rhino.head.Token.NOT: return Token.NOT; case com.google.javascript.rhino

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.javascript.rhino.ErrorReporter; /** * Value types (null, void, number, boolean, string). */ abstract class ValueType extends JSType { ValueType(JSTypeRegistry registry) { super(registry); } @Override final JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { return this; } @Override public boolean hasDisplayName() { return true; } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseValueType(this, that); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> sourceExpert.length() means something is missing // at the end of the line if (excerpt.equals(LINE) && 0 <= charno && charno <= sourceExcerpt.length()) { for (int i = 0; i < charno; i++) { char c = sourceExcerpt.charAt(i); if (Character.isWhitespace(c)) { b.append(c); } else { b.append(' '); } } b.append("^\n"); } } return b.toString(); } /** * Formats a region by appending line numbers in front, e.g. * <pre> 9| if (foo) { * 10| alert('bar'); * 11| }</pre> * and return line excerpt without any modification. */ static class LineNumberingFormatter implements ExcerptFormatter { @Override public String formatLine(String line, int lineNumber) { return line; } @Override public String formatRegion(Region region) { if (region == null) { return null; } String code = region.getSourceExcerpt(); if (code.length() == 0) { return null; } // max length of the number display int numberLength = Integer.toString(region.getEndingLineNumber()) .length(); // formatting StringBuilder builder = new StringBuilder(code.length() * 2); int start = 0; int end = code.indexOf('\n', start); int lineNumber = region.getBeginningLineNumber(); while (start >= 0) { // line extraction String line; if (end < 0) { line = code.substring(start); if (line.length() == 0) { return builder.substring(0, builder.length() - 1); } } else { line = code.substring(start, end); } builder.append(" "); // nice spaces for the line number int spaces = numberLength - Integer.toString(lineNumber).length(); builder.append(Strings.repeat(" ", spaces)); builder.append(lineNumber); builder.append("| "); // end & update if (end < 0) { builder.append(line); start = -1; } else { builder.append(line); builder.append('\n'); start = end + 1; end = code.indexOf('\n', start); lineNumber++; } } return builder.toString(); } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> } /** * Behavior that checks variables for redeclaration or early references * just after they go out of scope. */ private class ReferenceCheckingBehavior implements Behavior { @Override public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) { // TODO(bashir) In hot-swap version this means that for global scope we // only go through all global variables accessed in the modified file not // all global variables. This should be fixed. // Check all vars after finishing a scope for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); checkVar(v, referenceMap.getReferences(v).references); } } /** * If the variable is declared more than once in a basic block, generate a * warning. Also check if a variable is used in a given scope before it is * declared, which suggest a likely error. Relies on the fact that * references is in parse-tree order. */ private void checkVar(Var v, List<Reference> references) { blocksWithDeclarations.clear(); boolean isDeclaredInScope = false; boolean isUnhoistedNamedFunction = false; Reference hoistedFn = null; // Look for hoisted functions. for (Reference reference : references) { if (reference.isHoistedFunction()) { blocksWithDeclarations.add(reference.getBasicBlock()); isDeclaredInScope = true; hoistedFn = reference; break; } else if (NodeUtil.isFunctionDeclaration( reference.getNode().getParent())) { isUnhoistedNamedFunction = true; } } for (Reference reference : references) { if (reference == hoistedFn) { continue; } BasicBlock basicBlock = reference.getBasicBlock(); boolean isDeclaration = reference.isDeclaration(); boolean allowDupe = SyntacticScopeCreator.hasDuplicateDeclarationSuppression( reference.getNode(), v); if (isDeclaration && !allowDupe) { // Look through all the declarations we've found so far, and // check if any of them are before this block. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (declaredBlock.provablyExecutesBefore(basicBlock)) { // TODO(johnlenz): Fix AST generating clients that so they would // have property StaticSourceFile attached at each node. Or // better yet, make sure the generated code never violates // the requirement to pass aggressive var check! String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, REDECLARED_VARIABLE, v.name)); break; } } } if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) { // Only allow an unhoisted named function to be used within the // block it is declared. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (!declaredBlock.provablyExecutesBefore(basicBlock)) { String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), AMBIGUOUS_FUNCTION_

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>DECL, v.name)); break; } } } if (!isDeclaration && !isDeclaredInScope) { // Don't check the order of refer in externs files. if (!reference.getNode().isFromExterns()) { // Special case to deal with var goog = goog || {} Node grandparent = reference.getGrandparent(); if (grandparent.isName() && grandparent.getString() == v.name) { continue; } // Only generate warnings if the scopes do not match in order // to deal with possible forward declarations and recursion if (reference.getScope() == v.scope) { String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, UNDECLARED_REFERENCE, v.name)); } } } if (isDeclaration) { blocksWithDeclarations.add(basicBlock); isDeclaredInScope = true; } } } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import static com.google.common.base.Preconditions.checkState; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.javascript.rhino.ErrorReporter; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import java.util.Set; /** * The object type represents instances of JavaScript objects such as * {@code Object}, {@code Date}, {@code Function}.<p> * * Objects in JavaScript are unordered collections of properties. * Each property consists of a name, a value and a set of attributes.<p> * * Each instance has an implicit prototype property ({@code [[Prototype]]}) * pointing to an object instance, which itself has an implicit property, thus * forming a chain.<p> * * A class begins life with no name. Later, a name may be provided once it * can be inferred. Note that the name in this case is strictly for * debugging purposes. Looking up type name references goes through the * {@link JSTypeRegistry}.<p> */ class PrototypeObjectType extends ObjectType { private static final long serialVersionUID = 1L; private final String className; private final Property

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Map properties; private final boolean nativeType; // NOTE(nicksantos): The implicit prototype can change over time. // Modeling this is a bear. Always call getImplicitPrototype(), because // some subclasses override this to do special resolution handling. private ObjectType implicitPrototypeFallback; // If this is a function prototype, then this is the owner. // A PrototypeObjectType can only be the prototype of one function. If we try // to do this for multiple functions, then we'll have to create a new one. private FunctionType ownerFunction = null; // Whether the toString representation of this should be pretty-printed, // by printing all properties. private boolean prettyPrint = false; private static final int MAX_PRETTY_PRINTED_PROPERTIES = 4; /** * Creates an object type. * * @param className the name of the class. May be {@code null} to * denote an anonymous class. * * @param implicitPrototype the implicit prototype * (a.k.a. {@code [[Prototype]]}) as defined by ECMA-262. If the * implicit prototype is {@code null} the implicit prototype will be * set to the {@link JSTypeNative#OBJECT_TYPE}. */ PrototypeObjectType(JSTypeRegistry registry, String className, ObjectType implicitPrototype) { this(registry, className, implicitPrototype, false, null); } /** * Creates an object type, allowing specification of the implicit prototype, * whether the object is native, and any templatized types. */ PrototypeObjectType(JSTypeRegistry registry, String className, ObjectType implicitPrototype, boolean nativeType, TemplateTypeMap templateTypeMap) { super(registry, templateTypeMap); this.properties = new PropertyMap(); this.properties.setParentSource(this); this.className = className; this.nativeType = nativeType; if (nativeType || implicitPrototype != null) { setImplicitPrototype(implicitPrototype); } else { setImplicitPrototype( registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE)); } } @Override PropertyMap getPropertyMap() { return properties; } @Override boolean defineProperty(String name, JSType type, boolean inferred, Node propertyNode) { if (hasOwnDeclaredProperty(name)) { return false; } Property newProp = new Property( name, type, inferred, propertyNode); properties.putProperty(name, newProp); return true; } @Override public boolean removeProperty(String name) { return properties.removeProperty(name); } @Override public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) { if (info != null) { if (properties.getOwnProperty(propertyName) == null) { // If docInfo was attached, but the type of the property // was not defined anywhere, then we consider this an explicit // declaration of the property. defineInferredProperty(propertyName, getPropertyType(propertyName), null); } // The prototype property is not represented as a normal Property. // We probably don't want to attach any JSDoc to it anyway. Property property = properties.getOwnProperty(propertyName);

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>; } @Override public FunctionType getConstructor() { return null; } @Override public ObjectType getImplicitPrototype() { return implicitPrototypeFallback; } /** * This should only be reset on the FunctionPrototypeType, only to fix an * incorrectly established prototype chain due to the user having a mismatch * in super class declaration, and only before properties on that type are * processed. */ final void setImplicitPrototype(ObjectType implicitPrototype) { checkState(!hasCachedValues()); this.implicitPrototypeFallback = implicitPrototype; } @Override public String getReferenceName() { if (className != null) { return className; } else if (ownerFunction != null) { return ownerFunction.getReferenceName() + ".prototype"; } else { return null; } } @Override public boolean hasReferenceName() { return className != null || ownerFunction != null; } @Override public boolean isSubtype(JSType that) { if (JSType.isSubtypeHelper(this, that)) { return true; } // Union types if (that.isUnionType()) { // The static {@code JSType.isSubtype} check already decomposed // union types, so we don't need to check those again. return false; } // record types if (that.isRecordType()) { return RecordType.isSubtype(this, that.toMaybeRecordType()); } // Interfaces // Find all the interfaces implemented by this class and compare each one // to the interface instance. ObjectType thatObj = that.toObjectType(); FunctionType thatCtor = thatObj == null ? null : thatObj.getConstructor(); if (getConstructor() != null && getConstructor().isInterface()) { for (ObjectType thisInterface : getCtorExtendedInterfaces()) { if (thisInterface.isSubtype(that)) { return true; } } } else if (thatCtor != null && thatCtor.isInterface()) { Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces(); for (ObjectType thisInterface : thisInterfaces) { if (thisInterface.isSubtype(that)) { return true; } } } // other prototype based objects if (isUnknownType() || implicitPrototypeChainIsUnknown()) { // If unsure, say 'yes', to avoid spurious warnings. // TODO(user): resolve the prototype chain completely in all cases, // to avoid guessing. return true; } return thatObj != null && isImplicitPrototype(thatObj); } private boolean implicitPrototypeChainIsUnknown() { ObjectType p = getImplicitPrototype(); while (p != null) { if (p.isUnknownType()) { return true; } p = p.getImplicitPrototype(); } return false; } @Override public boolean hasCachedValues() { return super.hasCachedValues(); } /** Whether this is a built-in object. */ @Override public boolean isNativeObjectType() { return nativeType; } @Override void setOwnerFunction(FunctionType type) { Preconditions.checkState(ownerFunction == null || type == null); ownerFunction = type; } @Override public Function

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Type getOwnerFunction() { return ownerFunction; } @Override public Iterable<ObjectType> getCtorImplementedInterfaces() { return isFunctionPrototypeType() ? getOwnerFunction().getImplementedInterfaces() : ImmutableList.<ObjectType>of(); } @Override public Iterable<ObjectType> getCtorExtendedInterfaces() { return isFunctionPrototypeType() ? getOwnerFunction().getExtendedInterfaces() : ImmutableList.<ObjectType>of(); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); ObjectType implicitPrototype = getImplicitPrototype(); if (implicitPrototype != null) { implicitPrototypeFallback = (ObjectType) implicitPrototype.resolve(t, scope); } for (Property prop : properties.values()) { prop.setType(safeResolve(prop.getType(), t, scope)); } return this; } @Override public void matchConstraint(JSType constraint) { // We only want to match constraints on anonymous types. if (hasReferenceName()) { return; } // Handle the case where the constraint object is a record type. // // param constraint {{prop: (number|undefined)}} // function f(constraint) {} // f({}); // // We want to modify the object literal to match the constraint, by // taking any each property on the record and trying to match // properties on this object. if (constraint.isRecordType()) { matchRecordTypeConstraint(constraint.toObjectType()); } else if (constraint.isUnionType()) { for (JSType alt : constraint.toMaybeUnionType().getAlternates()) { if (alt.isRecordType()) { matchRecordTypeConstraint(alt.toObjectType()); } } } } public void matchRecordTypeConstraint(ObjectType constraintObj) { for (String prop : constraintObj.getOwnPropertyNames()) { JSType propType = constraintObj.getPropertyType(prop); if (!isPropertyTypeDeclared(prop)) { JSType typeToInfer = propType; if (!hasProperty(prop)) { typeToInfer = getNativeType(JSTypeNative.VOID_TYPE) .getLeastSupertype(propType); } defineInferredProperty(prop, typeToInfer, null); } } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>LineIndex() { return lineCount; } /** Returns the (zero-based) index of the last column in the text buffer. */ int getColumnIndex() { return colCount; } /** Determines whether the text ends with the given suffix. */ boolean endsWith(String suffix) { return (sb.length() > suffix.length()) && suffix.equals(sb.substring(sb.length() - suffix.length())); } } //------------------------------------------------------------------------ // Optimizations //------------------------------------------------------------------------ public void optimize() { // Ideally, this pass should be the first pass run, however: // 1) VariableReferenceCheck reports unexpected warnings if Normalize // is done first. // 2) ReplaceMessages, stripCode, and potentially custom passes rely on // unmodified local names. normalize(); // Create extern exports after the normalize because externExports depends on unique names. if (options.isExternExportsEnabled() || options.externExportsPath != null) { externExports(); } phaseOptimizer = new PhaseOptimizer(this, tracker, null); if (options.devMode == DevMode.EVERY_PASS) { phaseOptimizer.setSanityCheck(sanityCheck); } if (options.getCheckDeterminism()) { phaseOptimizer.setPrintAstHashcodes(true); } phaseOptimizer.consume(getPassConfig().getOptimizations()); phaseOptimizer.process(externsRoot, jsRoot); phaseOptimizer = null; } @Override void setCssRenamingMap(CssRenamingMap map) { options.cssRenamingMap = map; } @Override CssRenamingMap getCssRenamingMap() { return options.cssRenamingMap; } /** * Reprocesses the current defines over the AST. This is used by GwtCompiler * to generate N outputs for different targets from the same (checked) AST. * For each target, we apply the target-specific defines by calling * {@code processDefines} and then {@code optimize} to optimize the AST * specifically for that target. */ public void processDefines() { (new DefaultPassConfig(options)).processDefines.create(this) .process(externsRoot, jsRoot); } boolean isInliningForbidden() { return options.propertyRenaming == PropertyRenamingPolicy.HEURISTIC || options.propertyRenaming == PropertyRenamingPolicy.AGGRESSIVE_HEURISTIC; } /** Control Flow Analysis. */ ControlFlowGraph<Node> computeCFG() { logger.fine("Computing Control Flow Graph"); Tracer tracer = newTracer("computeCFG"); ControlFlowAnalysis cfa = new ControlFlowAnalysis(this, true, false); process(cfa); stopTracer(tracer, "computeCFG"); return cfa.getCfg(); } public void normalize() { logger.fine("Normalizing"); startPass("normalize"); process(new Normalize(this, false)); endPass(); } @Override void prepareAst(Node root) { CompilerPass pass = new PrepareAst(this); pass.process(null, root); } void recordFunctionInformation() { logger.fine("Recording function information"); startPass("

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> output targets without having to perform checking multiple times. * * NOTE: This does not include all parts of the compiler's internal state. In * particular, SourceFiles and CompilerOptions are not recorded. In * order to recreate a Compiler instance from scratch, you would need to * call {@code init} with the same arguments as in the initial creation before * restoring intermediate state. */ public static class IntermediateState implements Serializable { private static final long serialVersionUID = 1L; Node externsRoot; private Node jsRoot; private List<CompilerInput> externs; private List<CompilerInput> inputs; private List<JSModule> modules; private PassConfig.State passConfigState; private JSTypeRegistry typeRegistry; private AbstractCompiler.LifeCycleStage lifeCycleStage; private Map<String, Node> injectedLibraries; private IntermediateState() {} } /** * Returns the current internal state, excluding the input files and modules. */ public IntermediateState getState() { IntermediateState state = new IntermediateState(); state.externsRoot = externsRoot; state.jsRoot = jsRoot; state.externs = externs; state.inputs = inputs; state.modules = modules; state.passConfigState = getPassConfig().getIntermediateState(); state.typeRegistry = typeRegistry; state.lifeCycleStage = getLifeCycleStage(); state.injectedLibraries = Maps.newLinkedHashMap(injectedLibraries); return state; } /** * Sets the internal state to the capture given. Note that this assumes that * the input files are already set up. */ public void setState(IntermediateState state) { externsRoot = state.externsRoot; jsRoot = state.jsRoot; externs = state.externs; inputs = state.inputs; modules = state.modules; passes = createPassConfigInternal(); getPassConfig().setIntermediateState(state.passConfigState); typeRegistry = state.typeRegistry; setLifeCycleStage(state.lifeCycleStage); injectedLibraries.clear(); injectedLibraries.putAll(state.injectedLibraries); } @VisibleForTesting List<CompilerInput> getInputsForTesting() { return inputs; } @VisibleForTesting List<CompilerInput> getExternsForTesting() { return externs; } @Override boolean hasRegExpGlobalReferences() { return hasRegExpGlobalReferences; } @Override void setHasRegExpGlobalReferences(boolean references) { hasRegExpGlobalReferences = references; } @Override void updateGlobalVarReferences(Map<Var, ReferenceCollection> refMapPatch, Node collectionRoot) { Preconditions.checkState(collectionRoot.isScript() || collectionRoot.isBlock()); if (globalRefMap == null) { globalRefMap = new GlobalVarReferenceMap(getInputsInOrder(), getExternsInOrder()); } globalRefMap.updateGlobalVarReferences(refMapPatch, collectionRoot); } @Override GlobalVarReferenceMap getGlobalVarReferences() { return globalRefMap; } @Override CompilerInput getSynthesizedExternsInput() { if (synthesizedExternsInput

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> * If the programmer assigns other types of values to this property, * the property will take on the union of all these types. * * UNKNOWN properties are properties on the UNKNOWN type. The UNKNOWN * type has all properties, but we do not know whether they are * declared or inferred. * */ public abstract class ObjectType extends JSType implements StaticScope<JSType> { private boolean visited; private JSDocInfo docInfo = null; private boolean unknown = true; ObjectType(JSTypeRegistry registry) { super(registry); } ObjectType(JSTypeRegistry registry, TemplateTypeMap templateTypeMap) { super(registry, templateTypeMap); } @Override public Node getRootNode() { return null; } @Override public ObjectType getParentScope() { return getImplicitPrototype(); } /** * Returns the property map that manages the set of properties for an object. */ PropertyMap getPropertyMap() { return PropertyMap.immutableEmptyMap(); } /** * Default getSlot implementation. This gets overridden by FunctionType * for lazily-resolved prototypes. */ @Override public Property getSlot(String name) { return getPropertyMap().getSlot(name); } @Override public Property getOwnSlot(String name) { return getPropertyMap().getOwnProperty(name); } @Override public JSType getTypeOfThis() { return null; } /** * Gets the declared default element type. * @see TemplatizedType */ public ImmutableList<JSType> getTemplateTypes() { return null; } /** * Gets the docInfo for this type. */ @Override public JSDocInfo getJSDocInfo() { return docInfo; } /** * Sets the docInfo for this type from the given * {@link JSDocInfo}. The {@code JSDocInfo} may be {@code null}. */ public void setJSDocInfo(JSDocInfo info) { docInfo = info; } /** * Detects a cycle in the implicit prototype chain. This method accesses * the {@link #getImplicitPrototype()} method and must therefore be * invoked only after the object is sufficiently initialized to respond to * calls to this method.<p> * * @return True iff an implicit prototype cycle was detected. */ final boolean detectImplicitPrototypeCycle() { // detecting cycle this.visited = true; ObjectType p = getImplicitPrototype(); while (p != null) { if (p.visited) { return true; } else { p.visited = true; } p = p.getImplicitPrototype(); } // clean up p = this; do { p.visited = false; p = p.getImplicitPrototype(); } while (p != null); return false; } /** * Detects cycles in either the implicit prototype chain, or the implemented/extended * interfaces.<p> * * @return True iff a cycle was detected. */ final boolean detectInheritanceCycle() { // TODO(user): This should get moved to preventing cycles in FunctionTypeBuilder // rather than removing them here after they have been created. // Also,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> this doesn't do the right thing for extended interfaces, though that is // masked by another bug. return detectImplicitPrototypeCycle() || Iterables.contains(this.getCtorImplementedInterfaces(), this) || Iterables.contains(this.getCtorExtendedInterfaces(), this); } /** * Gets the reference name for this object. This includes named types * like constructors, prototypes, and enums. It notably does not include * literal types like strings and booleans and structural types. * @return the object's name or {@code null} if this is an anonymous * object */ public abstract String getReferenceName(); /** * Due to the complexity of some of our internal type systems, sometimes * we have different types constructed by the same constructor. * In other parts of the type system, these are called delegates. * We construct these types by appending suffixes to the constructor name. * * The normalized reference name does not have these suffixes, and as such, * recollapses these implicit types back to their real type. */ public String getNormalizedReferenceName() { String name = getReferenceName(); if (name != null) { int pos = name.indexOf("("); if (pos != -1) { return name.substring(0, pos); } } return name; } @Override public String getDisplayName() { return getNormalizedReferenceName(); } /** * Creates a suffix for a proxy delegate. * @see #getNormalizedReferenceName */ public static String createDelegateSuffix(String suffix) { return "(" + suffix + ")"; } /** * Returns true if the object is named. * @return true if the object is named, false if it is anonymous */ public boolean hasReferenceName() { return false; } @Override public TernaryValue testForEquality(JSType that) { // super TernaryValue result = super.testForEquality(that); if (result != null) { return result; } // objects are comparable to everything but null/undefined if (that.isSubtype( getNativeType(JSTypeNative.OBJECT_NUMBER_STRING_BOOLEAN))) { return UNKNOWN; } else { return FALSE; } } /** * Gets this object's constructor. * @return this object's constructor or {@code null} if it is a native * object (constructed natively v.s. by instantiation of a function) */ public abstract FunctionType getConstructor(); /** * Gets the implicit prototype (a.k.a. the {@code [[Prototype]]} property). */ public abstract ObjectType getImplicitPrototype(); /** * Defines a property whose type is explicitly declared by the programmer. * @param propertyName the property's name * @param type the type * @param propertyNode the node corresponding to the declaration of property * which might later be accessed using {@code getPropertyNode}. */ public final boolean defineDeclaredProperty(String propertyName, JSType type, Node propertyNode) { boolean result = defineProperty(propertyName, type, false, propertyNode); // All property definitions go through this method // or defineInferredProperty. Because the properties defined an an

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> // object can affect subtyping, it's slightly more efficient // to register this after defining the property. registry.registerPropertyOnType(propertyName, this); return result; } /** * Defines a property whose type is on a synthesized object. These objects * don't actually exist in the user's program. They're just used for * bookkeeping in the type system. */ public final boolean defineSynthesizedProperty(String propertyName, JSType type, Node propertyNode) { return defineProperty(propertyName, type, false, propertyNode); } /** * Defines a property whose type is inferred. * @param propertyName the property's name * @param type the type * @param propertyNode the node corresponding to the inferred definition of * property that might later be accessed using {@code getPropertyNode}. */ public final boolean defineInferredProperty(String propertyName, JSType type, Node propertyNode) { StaticSlot<JSType> originalSlot = getSlot(propertyName); if (hasProperty(propertyName)) { if (isPropertyTypeDeclared(propertyName)) { // We never want to hide a declared property with an inferred property. return true; } JSType originalType = getPropertyType(propertyName); type = originalType == null ? type : originalType.getLeastSupertype(type); } boolean result = defineProperty(propertyName, type, true, propertyNode); // All property definitions go through this method // or defineDeclaredProperty. Because the properties defined an an // object can affect subtyping, it's slightly more efficient // to register this after defining the property. registry.registerPropertyOnType(propertyName, this); return result; } /** * Defines a property.<p> * * For clarity, callers should prefer {@link #defineDeclaredProperty} and * {@link #defineInferredProperty}. * * @param propertyName the property's name * @param type the type * @param inferred {@code true} if this property's type is inferred * @param propertyNode the node that represents the definition of property. * Depending on the actual sub-type the node type might be different. * The general idea is to have an estimate of where in the source code * this property is defined. * @return True if the property was registered successfully, false if this * conflicts with a previous property type declaration. */ abstract boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode); /** * Removes the declared or inferred property from this ObjectType. * * @param propertyName the property's name * @return true if the property was removed successfully. False if the * property did not exist, or could not be removed. */ public boolean removeProperty(String propertyName) { return false; } /** * Gets the node corresponding to the definition of the specified property. * This could be the node corresponding to declaration of the property or the * node corresponding to the first reference to this property, e.g., * "this.propertyName" in a constructor. Note this is mainly intended to be * an estimate of where in the source code a property is defined. Sometime * the returned node is not even part of the global AST

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> but in the AST of the * JsDoc that defines a type. * * @param propertyName the name of the property * @return the {@code Node} corresponding to the property or null. */ public Node getPropertyNode(String propertyName) { Property p = getSlot(propertyName); return p == null ? null : p.getNode(); } /** * Gets the docInfo on the specified property on this type. This should not * be implemented recursively, as you generally need to know exactly on * which type in the prototype chain the JSDocInfo exists. */ public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) { Property p = getOwnSlot(propertyName); return p == null ? null : p.getJSDocInfo(); } /** * Sets the docInfo for the specified property from the * {@link JSDocInfo} on its definition. * @param info {@code JSDocInfo} for the property definition. May be * {@code null}. */ public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) { // by default, do nothing } @Override public JSType findPropertyType(String propertyName) { return hasProperty(propertyName) ? getPropertyType(propertyName) : null; } /** * Gets the property type of the property whose name is given. If the * underlying object does not have this property, the Unknown type is * returned to indicate that no information is available on this property. * * This gets overridden by FunctionType for lazily-resolved call() and * bind() functions. * * @return the property's type or {@link UnknownType}. This method never * returns {@code null}. */ public JSType getPropertyType(String propertyName) { StaticSlot<JSType> slot = getSlot(propertyName); if (slot == null) { if (isNoResolvedType() || isCheckedUnknownType()) { return getNativeType(JSTypeNative.CHECKED_UNKNOWN_TYPE); } else if (isEmptyType()) { return getNativeType(JSTypeNative.NO_TYPE); } return getNativeType(JSTypeNative.UNKNOWN_TYPE); } return slot.getType(); } @Override public boolean hasProperty(String propertyName) { // Unknown types have all properties. return isEmptyType() || isUnknownType() || getSlot(propertyName) != null; } /** * Checks whether the property whose name is given is present directly on * the object. Returns false even if it is declared on a supertype. */ public boolean hasOwnProperty(String propertyName) { return getOwnSlot(propertyName) != null; } /** * Returns the names of all the properties directly on this type. * * Overridden by FunctionType to add "prototype". */ public Set<String> getOwnPropertyNames() { return getPropertyMap().getOwnPropertyNames(); } /** * Checks whether the property's type is inferred. */ public boolean isPropertyTypeInferred(String propertyName) { StaticSlot<JSType> slot = getSlot(propertyName); return slot == null ? false : slot.isTypeInferred(); } /** * Checks whether

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> the property's type is declared. */ public boolean isPropertyTypeDeclared(String propertyName) { StaticSlot<JSType> slot = getSlot(propertyName); return slot == null ? false : !slot.isTypeInferred(); } /** * Whether the given property is declared on this object. */ final boolean hasOwnDeclaredProperty(String name) { return hasOwnProperty(name) && isPropertyTypeDeclared(name); } /** Checks whether the property was defined in the externs. */ public boolean isPropertyInExterns(String propertyName) { Property p = getSlot(propertyName); return p == null ? false : p.isFromExterns(); } /** * Gets the number of properties of this object. */ public int getPropertiesCount() { return getPropertyMap().getPropertiesCount(); } /** * Returns a list of properties defined or inferred on this type and any of * its supertypes. */ public Set<String> getPropertyNames() { Set<String> props = Sets.newTreeSet(); collectPropertyNames(props); return props; } /** * Adds any properties defined on this type or its supertypes to the set. */ final void collectPropertyNames(Set<String> props) { getPropertyMap().collectPropertyNames(props); } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseObjectType(this); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseObjectType(this, that); } /** * Checks that the prototype is an implicit prototype of this object. Since * each object has an implicit prototype, an implicit prototype's * implicit prototype is also this implicit prototype's. * * @param prototype any prototype based object * * @return {@code true} if {@code prototype} is {@code equal} to any * object in this object's implicit prototype chain. */ final boolean isImplicitPrototype(ObjectType prototype) { for (ObjectType current = this; current != null; current = current.getImplicitPrototype()) { if (current.isTemplatizedType()) { current = current.toMaybeTemplatizedType().getReferencedType(); } if (current.isEquivalentTo(prototype)) { return true; } } return false; } @Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { return BooleanLiteralSet.TRUE; } /** * We treat this as the unknown type if any of its implicit prototype * properties is unknown. */ @Override public boolean isUnknownType() { // If the object is unknown now, check the supertype again, // because it might have been resolved since the last check. if (unknown) { ObjectType implicitProto = getImplicitPrototype(); if (implicitProto == null || implicitProto.isNativeObjectType()) { unknown = false; for (ObjectType interfaceType : getCtorExtendedInterfaces()) { if (interfaceType.isUnknownType()) { unknown = true; break; } } } else { unknown = implicitProto.isUnknownType(); } } return unknown; }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> matches(error.getType()); } /** * Returns whether the given type matches a type in this group. */ public boolean matches(DiagnosticType type) { return types.contains(type); } /** * Returns whether all of the types in the given group are in this group. */ boolean isSubGroup(DiagnosticGroup group) { for (DiagnosticType type : group.types) { if (!matches(type)) { return false; } } return true; } /** * Returns an iterable over all the types in this group. */ public Iterable<DiagnosticType> getTypes() { return types; } @Override public String toString() { return name == null ? super.toString() : "DiagnosticGroup<" + name + ">"; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> the enclosing scope. */ @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) { // TODO(user): Investigate whether it is really necessary to keep two // different mechanisms for resolving named types, and if so, which order // makes more sense. Now, resolution via registry is first in order to // avoid triggering the warnings built into the resolution via properties. boolean resolved = resolveViaRegistry(t); if (detectInheritanceCycle()) { handleTypeCycle(t); } if (resolved) { super.resolveInternal(t, enclosing); finishPropertyContinuations(); return registry.isLastGeneration() ? getReferencedType() : this; } resolveViaProperties(t, enclosing); if (detectInheritanceCycle()) { handleTypeCycle(t); } super.resolveInternal(t, enclosing); if (isResolved()) { finishPropertyContinuations(); } return registry.isLastGeneration() ? getReferencedType() : this; } /** * Resolves a named type by looking it up in the registry. * @return True if we resolved successfully. */ private boolean resolveViaRegistry(ErrorReporter reporter) { JSType type = registry.getType(reference); if (type != null) { setReferencedAndResolvedType(type, reporter); return true; } return false; } /** * Resolves a named type by looking up its first component in the scope, and * subsequent components as properties. The scope must have been fully * parsed and a symbol table constructed. */ private void resolveViaProperties(ErrorReporter reporter, StaticScope<JSType> enclosing) { JSType value = lookupViaProperties(reporter, enclosing); // last component of the chain if (value != null && value.isFunctionType() && (value.isConstructor() || value.isInterface())) { FunctionType functionType = value.toMaybeFunctionType(); setReferencedAndResolvedType(functionType.getInstanceType(), reporter); } else if (value != null && value.isNoObjectType()) { setReferencedAndResolvedType( registry.getNativeFunctionType( JSTypeNative.NO_OBJECT_TYPE).getInstanceType(), reporter); } else if (value instanceof EnumType) { setReferencedAndResolvedType( ((EnumType) value).getElementsType(), reporter); } else { // We've been running into issues where people forward-declare // non-named types. (This is legitimate...our dependency management // code doubles as our forward-declaration code.) // // So if the type does resolve to an actual value, but it's not named, // then don't respect the forward declaration. handleUnresolvedType(reporter, value == null || value.isUnknownType()); } } /** * Resolves a type by looking up its first component in the scope, and * subsequent components as properties. The scope must have been fully * parsed and a symbol table constructed. * @return The type of the symbol, or null if the type could not be found. */ private JSType lookupViaProperties(ErrorReporter reporter, StaticScope<JSType> enclosing) { String[] componentNames = reference

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.split("\\.", -1); if (componentNames[0].length() == 0) { return null; } StaticSlot<JSType> slot = enclosing.getSlot(componentNames[0]); if (slot == null) { return null; } // If the first component has a type of 'Unknown', then any type // names using it should be regarded as silently 'Unknown' rather than be // noisy about it. JSType slotType = slot.getType(); if (slotType == null || slotType.isAllType() || slotType.isNoType()) { return null; } JSType value = getTypedefType(reporter, slot); if (value == null) { return null; } // resolving component by component for (int i = 1; i < componentNames.length; i++) { ObjectType parentClass = ObjectType.cast(value); if (parentClass == null) { return null; } if (componentNames[i].length() == 0) { return null; } value = parentClass.getPropertyType(componentNames[i]); } return value; } private void setReferencedAndResolvedType( JSType type, ErrorReporter reporter) { if (validator != null) { validator.apply(type); } setReferencedType(type); checkEnumElementCycle(reporter); checkProtoCycle(reporter); setResolvedTypeInternal(getReferencedType()); } private void handleTypeCycle(ErrorReporter t) { setReferencedType( registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE)); t.warning("Cycle detected in inheritance chain of type " + reference, sourceName, lineno, charno); setResolvedTypeInternal(getReferencedType()); } private void checkEnumElementCycle(ErrorReporter t) { JSType referencedType = getReferencedType(); if (referencedType instanceof EnumElementType && ((EnumElementType) referencedType).getPrimitiveType() == this) { handleTypeCycle(t); } } private void checkProtoCycle(ErrorReporter t) { JSType referencedType = getReferencedType(); if (referencedType == this) { handleTypeCycle(t); } } // Warns about this type being unresolved iff it's not a forward-declared // type name. private void handleUnresolvedType( ErrorReporter t, boolean ignoreForwardReferencedTypes) { if (registry.isLastGeneration()) { boolean isForwardDeclared = ignoreForwardReferencedTypes && registry.isForwardDeclaredType(reference); if (!isForwardDeclared && registry.isLastGeneration()) { t.warning("Bad type annotation. Unknown type " + reference, sourceName, lineno, charno); } else { setReferencedType( registry.getNativeObjectType( JSTypeNative.NO_RESOLVED_TYPE)); if (registry.isLastGeneration() && validator != null) { validator.apply(getReferencedType()); } } setResolvedTypeInternal(getReferencedType()); } else { setResolvedTypeInternal(this); } } private JSType getTypedefType(ErrorReporter t, StaticSlot<JSType> slot) { JSType type

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> = slot.getType(); if (type != null) { return type; } handleUnresolvedType(t, true); return null; } @Override public boolean setValidator(Predicate<JSType> validator) { // If the type is already resolved, we can validate it now. If // the type has not been resolved yet, we need to wait till its // resolved before we can validate it. if (this.isResolved()) { return super.setValidator(validator); } else { this.validator = validator; return true; } } /** Store enough information to define a property at a later time. */ private static final class PropertyContinuation { private final String propertyName; private final JSType type; private final boolean inferred; private final Node propertyNode; private PropertyContinuation( String propertyName, JSType type, boolean inferred, Node propertyNode) { this.propertyName = propertyName; this.type = type; this.inferred = inferred; this.propertyNode = propertyNode; } void commit(ObjectType target) { target.defineProperty( propertyName, type, inferred, propertyNode); } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * <p>The syntactic scope creator scans the parse tree to create a Scope object * containing all the variable declarations in that scope.</p> * * <p>This implementation is not thread-safe.</p> * */ class SyntacticScopeCreator implements ScopeCreator { private final AbstractCompiler compiler; private Scope scope; private InputId inputId; private final RedeclarationHandler redeclarationHandler; // The arguments variable is special, in that it's declared in every local // scope, but not explicitly declared. private static final String ARGUMENTS = "arguments"; public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR = DiagnosticType.error( "JSC_VAR_MULTIPLY_DECLARED_ERROR", "Variable {0} first declared in {1}"); public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR = DiagnosticType.error( "JSC_VAR_ARGUMENTS_SHADOWED_ERROR", "Shadowing \"arguments\" is not allowed"); /** * Creates a ScopeCreator. */ SyntacticScopeCreator(AbstractCompiler compiler) { this.compiler = compiler; this.redeclarationHandler = new DefaultRedeclarationHandler(); } SyntacticScopeCreator( AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) { this.compiler = compiler; this.redeclarationHandler = redeclarationHandler; } @Override public Scope createScope(Node n, Scope parent) { inputId = null; if (parent == null) { scope = Scope.createGlobalScope(n); } else { scope = new Scope(parent, n); } scanRoot(n); inputId = null; Scope returnedScope = scope; scope = null; return returnedScope; } private void scanRoot(Node n) { if (n.isFunction()) { if (inputId == null) { inputId = NodeUtil.getInputId(n); // TODO(johnlenz): inputId maybe null if the FUNCTION node is detached // from the AST. // Is it meaningful to build a scope for detached FUNCTION node? } final Node fnNameNode = n.getFirstChild(); final

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> Node args = fnNameNode.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnNameNode); } // Args: Declare function variables Preconditions.checkState(args.isParamList()); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.isName()); declareVar(a); } // Body scanVars(body); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); declareVar(child); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(n.getFirstChild()); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().isName()); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var); scanVars(block); return; // only one child to scan case Token.SCRIPT: inputId = n.getInputId(); Preconditions.checkNotNull(inputId); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration( Scope s, String name, Node n, CompilerInput input); } /** * The default handler for duplicate declarations. */ private class DefaultRedeclarationHandler implements RedeclarationHandler { @Override public void onRedeclaration( Scope s, String name, Node n, CompilerInput input) { Node parent = n.getParent(); // Don't allow multiple variables to be declared at the top-level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar(name);

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> Node origParent = origVar.getParentNode(); if (origParent.isCatch() && parent.isCatch()) { // Okay, both are 'catch(x)' variables. return; } boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar); if (!allowDupe) { compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_MULTIPLY_DECLARED_ERROR, name, (origVar.input != null ? origVar.input.getName() : "??"))); } } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) { // Disallow shadowing "arguments" as we can't handle with our current // scope modeling. compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_ARGUMENTS_SHADOWED_ERROR)); } } } /** * Declares a variable. * * @param n The node corresponding to the variable name. */ private void declareVar(Node n) { Preconditions.checkState(n.isName()); CompilerInput input = compiler.getInput(inputId); String name = n.getString(); if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) { redeclarationHandler.onRedeclaration( scope, name, n, input); } else { scope.declare(name, n, null, input); } } /** * @param n The name node to check. * @param origVar The associated Var. * @return Whether duplicated declarations warnings should be suppressed * for the given node. */ static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); Node origParent = origVar.getParentNode(); JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } if (info != null && info.getSuppressions().contains("duplicate")) { return true; } info = origVar.nameNode.getJSDocInfo(); if (info == null) { info = origParent.getJSDocInfo(); } return (info != null && info.getSuppressions().contains("duplicate")); } /** * Generates an untyped global scope from the root of AST of compiler (which * includes externs). * * @param compiler The compiler for which the scope is generated. * @return The new untyped global scope generated as a result of this call. */ static Scope generateUntypedTopScope(AbstractCompiler compiler) { return new SyntacticScopeCreator(compiler).createScope(compiler.getRoot(), null); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.out = outState; } L getIn() { return in; } void setIn(L in) { Preconditions.checkNotNull(in); this.in = in; } List<L> getOut() { return out; } void setOut(List<L> out) { Preconditions.checkNotNull(out); for (L item : out) { Preconditions.checkNotNull(item); } this.out = out; } @Override public String toString() { return String.format("IN: %s OUT: %s", in, out); } @Override public int hashCode() { return Objects.hashCode(in, out); } } /** * Compute set of escaped variables. When a variable is escaped in a * dataflow analysis, it can be reference outside of the code that we are * analyzing. A variable is escaped if any of the following is true: * * <p><ol> * <li>It is defined as the exception name in CATCH clause so it became a * variable local not to our definition of scope.</li> * <li>Exported variables as they can be needed after the script terminates. * </li> * <li>Names of named functions because in JavaScript, <i>function foo(){}</i> * does not kill <i>foo</i> in the dataflow.</li> */ static void computeEscaped(final Scope jsScope, final Set<Var> escaped, AbstractCompiler compiler) { // TODO(user): Very good place to store this information somewhere. AbstractPostOrderCallback finder = new AbstractPostOrderCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (jsScope == t.getScope() || !n.isName() || parent.isFunction()) { return; } String name = n.getString(); Var var = t.getScope().getVar(name); if (var != null && var.scope == jsScope) { escaped.add(jsScope.getVar(name)); } } }; NodeTraversal t = new NodeTraversal(compiler, finder); t.traverseAtScope(jsScope); // 1: Remove the exception name in CATCH which technically isn't local to // begin with. for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) { Var var = i.next(); if (var.getParentNode().isCatch() || compiler.getCodingConvention().isExported(var.getName())) { escaped.add(var); } } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> strict mode"); static final DiagnosticType BAD_FUNCTION_DECLARATION = DiagnosticType.error( "JSC_BAD_FUNCTION_DECLARATION", "functions can only be declared at top level or immediately within " + "another function in ES5 strict mode"); private final AbstractCompiler compiler; private final boolean noVarCheck; private final boolean noCajaChecks; StrictModeCheck(AbstractCompiler compiler) { this(compiler, false, false); } StrictModeCheck( AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) { this.compiler = compiler; this.noVarCheck = noVarCheck; this.noCajaChecks = noCajaChecks; } @Override public void process(Node externs, Node root) { NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); NodeTraversal.traverse(compiler, root, new NonExternChecks()); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isFunction()) { checkFunctionUse(t, n); } else if (n.isName()) { if (!isDeclaration(n)) { checkNameUse(t, n); } } else if (n.isAssign()) { checkAssignment(t, n); } else if (n.isDelProp()) { checkDelete(t, n); } else if (n.isObjectLit()) { checkObjectLiteral(t, n); } else if (n.isLabel()) { checkLabel(t, n); } } /** Checks that the function is used legally. */ private void checkFunctionUse(NodeTraversal t, Node n) { if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) { t.report(n, BAD_FUNCTION_DECLARATION); } } /** * Determines if the given name is a declaration, which can be a declaration * of a variable, function, or argument. */ private static boolean isDeclaration(Node n) { switch (n.getParent().getType()) { case Token.VAR: case Token.FUNCTION: case Token.CATCH: return true; case Token.PARAM_LIST: return n.getParent().getParent().isFunction(); default: return false; } } /** Checks that the given name is used legally. */ private void checkNameUse(NodeTraversal t, Node n) { Var v = t.getScope().getVar(n.getString()); if (v == null) { // In particular, this prevents creating a global variable by assigning // to it without a declaration. if (!noVarCheck) { t.report(n, UNKNOWN_VARIABLE, n.getString()); } } if (!noCajaChecks) { if ("eval".equals(n.getString())) { t.report(n, EVAL_USE); } else if (n.getString().endsWith("__")) { t.report(n, ILLEGAL_NAME); } } } /** Checks that an assignment is not to the "arguments" object. */ private void checkAssignment(NodeTraversal

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> t, Node n) { if (n.getFirstChild().isName()) { if ("arguments".equals(n.getFirstChild().getString())) { t.report(n, ARGUMENTS_ASSIGNMENT); } else if ("eval".equals(n.getFirstChild().getString())) { // Note that assignment to eval is already illegal because any use of // that name is illegal. if (noCajaChecks) { t.report(n, EVAL_ASSIGNMENT); } } } } /** Checks that variables, functions, and arguments are not deleted. */ private void checkDelete(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { Var v = t.getScope().getVar(n.getFirstChild().getString()); if (v != null) { t.report(n, DELETE_VARIABLE); } } } /** Checks that object literal keys are valid. */ private void checkObjectLiteral(NodeTraversal t, Node n) { Set<String> getters = Sets.newHashSet(); Set<String> setters = Sets.newHashSet(); for (Node key = n.getFirstChild(); key != null; key = key.getNext()) { if (!noCajaChecks && key.getString().endsWith("__")) { t.report(key, ILLEGAL_NAME); } if (!key.isSetterDef()) { // normal property and getter cases if (getters.contains(key.getString())) { t.report(key, DUPLICATE_OBJECT_KEY); } else { getters.add(key.getString()); } } if (!key.isGetterDef()) { // normal property and setter cases if (setters.contains(key.getString())) { t.report(key, DUPLICATE_OBJECT_KEY); } else { setters.add(key.getString()); } } } } /** Checks that label names are valid. */ private void checkLabel(NodeTraversal t, Node n) { if (n.getFirstChild().getString().endsWith("__")) { if (!noCajaChecks) { t.report(n.getFirstChild(), ILLEGAL_NAME); } } } /** Checks that are performed on non-extern code only. */ private class NonExternChecks extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if ((n.isName()) && isDeclaration(n)) { checkDeclaration(t, n); } else if (n.isGetProp()) { checkProperty(t, n); } } /** Checks for illegal declarations. */ private void checkDeclaration(NodeTraversal t, Node n) { if ("eval".equals(n.getString())) { t.report(n, EVAL_DECLARATION); } else if ("arguments".equals(n.getString())) { t.report(n, ARGUMENTS_DECLARATION); } else if (n.getString().endsWith("__")) { if (!noCajaChecks) { t.report(n, ILLEGAL_NAME); } } } /** Checks for illegal property accesses.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> this.elementsType = new EnumElementType(registry, elementsType, name); } /** * Gets the source node or null if this is an unknown enum. */ public Node getSource() { return source; } @Override public EnumType toMaybeEnumType() { return this; } @Override public ObjectType getImplicitPrototype() { return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE); } /** * Gets the elements defined on this enum. * @return the elements' names defined on this enum. The returned set is * immutable. */ public Set<String> getElements() { return Collections.unmodifiableSet(elements); } /** * Defines a new element on this enum. * @param name the name of the new element * @param definingNode the {@code Node} that defines this new element * @return true iff the new element is added successfully */ public boolean defineElement(String name, Node definingNode) { elements.add(name); return defineDeclaredProperty(name, elementsType, definingNode); } /** * Gets the elements' type. */ public EnumElementType getElementsType() { return elementsType; } @Override public TernaryValue testForEquality(JSType that) { TernaryValue result = super.testForEquality(that); if (result != null) { return result; } return this.isEquivalentTo(that) ? TRUE : FALSE; } @Override public boolean isSubtype(JSType that) { return that.isEquivalentTo(getNativeType(JSTypeNative.OBJECT_TYPE)) || that.isEquivalentTo(getNativeType(JSTypeNative.OBJECT_PROTOTYPE)) || JSType.isSubtypeHelper(this, that); } @Override String toStringHelper(boolean forAnnotations) { return forAnnotations ? "Object" : getReferenceName(); } @Override public String getDisplayName() { return elementsType.getDisplayName(); } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseObjectType(this); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseObjectType(this, that); } @Override public FunctionType getConstructor() { return null; } @Override public boolean matchesNumberContext() { return false; } @Override public boolean matchesStringContext() { return true; } @Override public boolean matchesObjectContext() { return true; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { elementsType = (EnumElementType) elementsType.resolve(t, scope); return super.resolveInternal(t, scope); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Type MESSAGE_NOT_INITIALIZED_USING_NEW_SYNTAX = DiagnosticType.error("JSC_MSG_NOT_INITIALIZED_USING_NEW_SYNTAX", "message not initialized using " + MSG_FUNCTION_NAME); static final DiagnosticType BAD_FALLBACK_SYNTAX = DiagnosticType.error("JSC_MSG_BAD_FALLBACK_SYNTAX", String.format( "Bad syntax. " + "Expected syntax: goog.getMsgWithFallback(MSG_1, MSG_2)", MSG_FALLBACK_FUNCTION_NAME)); static final DiagnosticType FALLBACK_ARG_ERROR = DiagnosticType.error("JSC_MSG_FALLBACK_ARG_ERROR", "Could not find message entry for fallback argument {0}"); private static final String PH_JS_PREFIX = "{$"; private static final String PH_JS_SUFFIX = "}"; static final String MSG_PREFIX = "MSG_"; /** * Pattern for unnamed messages. * <p> * All JS messages in JS code should have unique name but messages in * generated code (i.e. from soy template) could have duplicated message names. * Later we replace the message names with ids constructed as a hash of the * message content. * <p> * <link href="http://code.google.com/p/closure-templates/"> * Soy</link> generates messages with names MSG_UNNAMED_<NUMBER> . This * pattern recognizes such messages. */ private static final Pattern MSG_UNNAMED_PATTERN = Pattern.compile("MSG_UNNAMED_\\d+"); private static final Pattern CAMELCASE_PATTERN = Pattern.compile("[a-z][a-zA-Z\\d]*[_\\d]*"); static final String HIDDEN_DESC_PREFIX = "@hidden"; // For old-style JS messages private static final String DESC_SUFFIX = "_HELP"; private final boolean needToCheckDuplications; private final JsMessage.Style style; private final JsMessage.IdGenerator idGenerator; final AbstractCompiler compiler; /** * The names encountered associated with their defining node and source. We * use it for tracking duplicated message ids in the source code. */ private final Map<String, MessageLocation> messageNames = Maps.newHashMap(); private final Map<Var, JsMessage> unnamedMessages = Maps.newHashMap(); /** * List of found goog.getMsg call nodes. * * When we visit goog.getMsg() node we add a pair node:sourcename and later * when we visit its parent we remove this pair. All nodes that are left at * the end of traversing are orphaned nodes. It means have no corresponding * var or property node. */ private final Map<Node, String> googMsgNodes = Maps.newHashMap(); private final CheckLevel checkLevel; /** * Creates JS message visitor. * * @param compiler the compiler instance * @param needToCheckDuplications whether to check duplicated messages in * traversed * @param style style that should be used during parsing * @param idGenerator generator that used for creating unique ID for the * message */ JsMessageVisitor(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>AbstractCompiler compiler, boolean needToCheckDuplications, JsMessage.Style style, JsMessage.IdGenerator idGenerator) { this.compiler = compiler; this.needToCheckDuplications = needToCheckDuplications; this.style = style; this.idGenerator = idGenerator; checkLevel = (style == JsMessage.Style.CLOSURE) ? CheckLevel.ERROR : CheckLevel.WARNING; // TODO(anatol): add flag that decides whether to process UNNAMED messages. // Some projects would not want such functionality (unnamed) as they don't // use SOY templates. } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); for (Map.Entry<Node, String> msgNode : googMsgNodes.entrySet()) { compiler.report(JSError.make(msgNode.getValue(), msgNode.getKey(), checkLevel, MESSAGE_NODE_IS_ORPHANED)); } } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { String messageKey; boolean isVar; Node msgNode, msgNodeParent; switch (node.getType()) { case Token.NAME: // var MSG_HELLO = 'Message' if ((parent != null) && (parent.isVar())) { messageKey = node.getString(); isVar = true; } else { return; } msgNode = node.getFirstChild(); msgNodeParent = node; break; case Token.ASSIGN: // somenamespace.someclass.MSG_HELLO = 'Message' isVar = false; Node getProp = node.getFirstChild(); if (!getProp.isGetProp()) { return; } Node propNode = getProp.getLastChild(); messageKey = propNode.getString(); msgNode = node.getLastChild(); msgNodeParent = node; break; case Token.CALL: // goog.getMsg() String fnName = node.getFirstChild().getQualifiedName(); if (MSG_FUNCTION_NAME.equals(fnName)) { googMsgNodes.put(node, traversal.getSourceName()); } else if (MSG_FALLBACK_FUNCTION_NAME.equals(fnName)) { visitFallbackFunctionCall(traversal, node); } return; default: return; } // Is this a message name? boolean isNewStyleMessage = msgNode != null && msgNode.isCall(); if (!isMessageName(messageKey, isNewStyleMessage)) { return; } if (msgNode == null) { compiler.report( traversal.makeError(node, MESSAGE_HAS_NO_VALUE, messageKey)); return; } // Just report a warning if a qualified messageKey that looks like a message // (e.g. "a.b.MSG_X") doesn't use goog.getMsg(). if (isNewStyleMessage) { googMsgNodes.remove(msgNode); } else if (style != JsMessage.Style.LEGACY) { compiler.report(traversal.makeError(node, checkLevel, MESSAGE_NOT_INITIALIZED_USING

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>_NEW_SYNTAX)); } boolean isUnnamedMsg = isUnnamedMessageName(messageKey); Builder builder = new Builder( isUnnamedMsg ? null : messageKey); builder.setSourceName(traversal.getSourceName()); try { if (isVar) { extractMessageFromVariable(builder, node, parent, parent.getParent()); } else { extractMessageFromProperty(builder, node.getFirstChild(), node); } } catch (MalformedException ex) { compiler.report(traversal.makeError(ex.getNode(), MESSAGE_TREE_MALFORMED, ex.getMessage())); return; } JsMessage extractedMessage = builder.build(idGenerator); // If asked to check named internal messages. if (needToCheckDuplications && !isUnnamedMsg && !extractedMessage.isExternal()) { checkIfMessageDuplicated(messageKey, msgNode); } trackMessage(traversal, extractedMessage, messageKey, msgNode, isUnnamedMsg); if (extractedMessage.isEmpty()) { // value of the message is an empty string. Translators do not like it. compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_TEXT, messageKey)); } // New-style messages must have descriptions. We don't emit a warning // for legacy-style messages, because there are thousands of // them in legacy code that are not worth the effort to fix, since they've // already been translated anyway. String desc = extractedMessage.getDesc(); if (isNewStyleMessage && (desc == null || desc.trim().isEmpty()) && !extractedMessage.isExternal()) { compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_DESCRIPTION, messageKey)); } JsMessageDefinition msgDefinition = new JsMessageDefinition( node, msgNode, msgNodeParent); processJsMessage(extractedMessage, msgDefinition); } /** * Track a message for later retrieval. * * This is used for tracking duplicates, and for figuring out message * fallback. Not all message types are trackable, because that would * require a more sophisticated analysis. e.g., * function f(s) { s.MSG_UNNAMED_X = 'Some untrackable message'; } */ private void trackMessage( NodeTraversal t, JsMessage message, String msgName, Node msgNode, boolean isUnnamedMessage) { if (!isUnnamedMessage) { MessageLocation location = new MessageLocation(message, msgNode); messageNames.put(msgName, location); } else if (msgNode.isName()) { Var var = t.getScope().getVar(msgName); if (var != null) { unnamedMessages.put(var, message); } } } /** Get a previously tracked message. */ private JsMessage getTrackedMessage(NodeTraversal t, String msgName) { boolean isUnnamedMessage = isUnnamedMessageName(msgName); if (!isUnnamedMessage) { MessageLocation location = messageNames.get(msgName); return location == null ? null : location.message; } else { Var var = t.getScope().getVar(msgName);

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> if (var != null) { return unnamedMessages.get(var); } } return null; } /** * Checks if message already processed. If so - it generates 'message * duplicated' compiler error. * * @param msgName the name of the message * @param msgNode the node that represents JS message */ private void checkIfMessageDuplicated(String msgName, Node msgNode) { if (messageNames.containsKey(msgName)) { MessageLocation location = messageNames.get(msgName); compiler.report(JSError.make(msgNode, MESSAGE_DUPLICATE_KEY, msgName, location.messageNode.getSourceFileName(), Integer.toString(location.messageNode.getLineno()))); } } /** * Creates a {@link JsMessage} for a JS message defined using a JS variable * declaration (e.g <code>var MSG_X = ...;</code>). * * @param builder the message builder * @param nameNode a NAME node for a JS message variable * @param parentNode a VAR node, parent of {@code nameNode} * @param grandParentNode the grandparent of {@code nameNode}. This node is * only used to get meta data about the message that might be * surrounding it (e.g. a message description). This argument may be * null if the meta data is not needed. * @throws MalformedException if {@code varNode} does not * correspond to a valid JS message VAR node */ private void extractMessageFromVariable( Builder builder, Node nameNode, Node parentNode, @Nullable Node grandParentNode) throws MalformedException { // Determine the message's value Node valueNode = nameNode.getFirstChild(); switch (valueNode.getType()) { case Token.STRING: case Token.ADD: maybeInitMetaDataFromJsDocOrHelpVar(builder, parentNode, grandParentNode); builder.appendStringPart(extractStringFromStringExprNode(valueNode)); break; case Token.FUNCTION: maybeInitMetaDataFromJsDocOrHelpVar(builder, parentNode, grandParentNode); extractFromFunctionNode(builder, valueNode); break; case Token.CALL: maybeInitMetaDataFromJsDoc(builder, parentNode); extractFromCallNode(builder, valueNode); break; default: throw new MalformedException("Cannot parse value of message " + builder.getKey(), valueNode); } } /** * Creates a {@link JsMessage} for a JS message defined using an assignment to * a qualified name (e.g <code>a.b.MSG_X = goog.getMsg(...);</code>). * * @param builder the message builder * @param getPropNode a GETPROP node in a JS message assignment * @param assignNode an ASSIGN node, parent of {@code getPropNode}. * @throws MalformedException if {@code getPropNode} does not * correspond to a valid JS message node */ private void extractMessageFromProperty( Builder builder, Node getPropNode, Node assignNode) throws MalformedException { Node callNode = getPropNode.getNext(); maybeInitMetaDataFrom

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>JsDoc(builder, assignNode); extractFromCallNode(builder, callNode); } /** * Initializes the meta data in a JsMessage by examining the nodes just before * and after a message VAR node. * * @param builder the message builder whose meta data will be initialized * @param varNode the message VAR node * @param parentOfVarNode {@code varNode}'s parent node */ private void maybeInitMetaDataFromJsDocOrHelpVar( Builder builder, Node varNode, @Nullable Node parentOfVarNode) throws MalformedException { // First check description in @desc if (maybeInitMetaDataFromJsDoc(builder, varNode)) { return; } // Check the preceding node for meta data if ((parentOfVarNode != null) && maybeInitMetaDataFromHelpVar(builder, parentOfVarNode.getChildBefore(varNode))) { return; } // Check the subsequent node for meta data maybeInitMetaDataFromHelpVar(builder, varNode.getNext()); } /** * Initializes the meta data in a JsMessage by examining a node just before or * after a message VAR node. * * @param builder the message builder whose meta data will be initialized * @param sibling a node adjacent to the message VAR node * @return true iff message has corresponding description variable */ private boolean maybeInitMetaDataFromHelpVar(Builder builder, @Nullable Node sibling) throws MalformedException { if ((sibling != null) && (sibling.isVar())) { Node nameNode = sibling.getFirstChild(); String name = nameNode.getString(); if (name.equals(builder.getKey() + DESC_SUFFIX)) { Node valueNode = nameNode.getFirstChild(); String desc = extractStringFromStringExprNode(valueNode); if (desc.startsWith(HIDDEN_DESC_PREFIX)) { builder.setDesc(desc.substring(HIDDEN_DESC_PREFIX.length()).trim()); builder.setIsHidden(true); } else { builder.setDesc(desc); } return true; } } return false; } /** * Initializes the meta data in a message builder given a node that may * contain JsDoc properties. * * @param builder the message builder whose meta data will be initialized * @param node the node with the message's JSDoc properties * @return true if message has JsDoc with valid description in @desc * annotation */ private boolean maybeInitMetaDataFromJsDoc(Builder builder, Node node) { boolean messageHasDesc = false; JSDocInfo info = node.getJSDocInfo(); if (info != null) { String desc = info.getDescription(); if (desc != null) { builder.setDesc(desc); messageHasDesc = true; } if (info.isHidden()) { builder.setIsHidden(true); } if (info.getMeaning() != null) { builder.setMeaning(info.getMeaning()); } } return messageHasDesc; } /** * Returns the string value associated with a node representing a JS string or * several JS strings added together (e.g. {@code 'str'} or {@

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>code 's' + 't' + * 'r'}). * * @param node the node from where we extract the string * @return String representation of the node * @throws MalformedException if the parsed message is invalid */ private static String extractStringFromStringExprNode(Node node) throws MalformedException { switch (node.getType()) { case Token.STRING: return node.getString(); case Token.ADD: StringBuilder sb = new StringBuilder(); for (Node child : node.children()) { sb.append(extractStringFromStringExprNode(child)); } return sb.toString(); default: throw new MalformedException("STRING or ADD node expected; found: " + getReadableTokenName(node), node); } } /** * Initializes a message builder from a FUNCTION node. * <p> * <pre> * The tree should look something like: * * function * |-- name * |-- lp * | |-- name <arg1> * | -- name <arg2> * -- block * | * --return * | * --add * |-- string foo * -- name <arg1> * </pre> * * @param builder the message builder * @param node the function node that contains a message * @throws MalformedException if the parsed message is invalid */ private void extractFromFunctionNode(Builder builder, Node node) throws MalformedException { Set<String> phNames = Sets.newHashSet(); for (Node fnChild : node.children()) { switch (fnChild.getType()) { case Token.NAME: // This is okay. The function has a name, but it is empty. break; case Token.PARAM_LIST: // Parse the placeholder names from the function argument list. for (Node argumentNode : fnChild.children()) { if (argumentNode.isName()) { String phName = argumentNode.getString(); if (phNames.contains(phName)) { throw new MalformedException("Duplicate placeholder name: " + phName, argumentNode); } else { phNames.add(phName); } } } break; case Token.BLOCK: // Build the message's value by examining the return statement Node returnNode = fnChild.getFirstChild(); if (!returnNode.isReturn()) { throw new MalformedException("RETURN node expected; found: " + getReadableTokenName(returnNode), returnNode); } for (Node child : returnNode.children()) { extractFromReturnDescendant(builder, child); } // Check that all placeholders from the message text have appropriate // object literal keys for (String phName : builder.getPlaceholders()) { if (!phNames.contains(phName)) { throw new MalformedException( "Unrecognized message placeholder referenced: " + phName, returnNode); } } break; default: throw new MalformedException( "NAME, LP, or BLOCK node expected; found: " + getReadableTokenName(node), fnChild); } } } /** * App

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>ends value parts to the message builder by traversing the descendants * of the given RETURN node. * * @param builder the message builder * @param node the node from where we extract a message * @throws MalformedException if the parsed message is invalid */ private void extractFromReturnDescendant(Builder builder, Node node) throws MalformedException { switch (node.getType()) { case Token.STRING: builder.appendStringPart(node.getString()); break; case Token.NAME: builder.appendPlaceholderReference(node.getString()); break; case Token.ADD: for (Node child : node.children()) { extractFromReturnDescendant(builder, child); } break; default: throw new MalformedException( "STRING, NAME, or ADD node expected; found: " + getReadableTokenName(node), node); } } /** * Initializes a message builder from a CALL node. * <p> * The tree should look something like: * * <pre> * call * |-- getprop * | |-- name 'goog' * | +-- string 'getMsg' * | * |-- string 'Hi {$userName}! Welcome to {$product}.' * +-- objlit * |-- string 'userName' * |-- name 'someUserName' * |-- string 'product' * +-- call * +-- name 'getProductName' * </pre> * * @param builder the message builder * @param node the call node from where we extract the message * @throws MalformedException if the parsed message is invalid */ private void extractFromCallNode(Builder builder, Node node) throws MalformedException { // Check the function being called if (!node.isCall()) { throw new MalformedException( "Message must be initialized using " + MSG_FUNCTION_NAME + " function.", node); } Node fnNameNode = node.getFirstChild(); if (!MSG_FUNCTION_NAME.equals(fnNameNode.getQualifiedName())) { throw new MalformedException( "Message initialized using unrecognized function. " + "Please use " + MSG_FUNCTION_NAME + "() instead.", fnNameNode); } // Get the message string Node stringLiteralNode = fnNameNode.getNext(); if (stringLiteralNode == null) { throw new MalformedException("Message string literal expected", stringLiteralNode); } // Parse the message string and append parts to the builder parseMessageTextNode(builder, stringLiteralNode); Node objLitNode = stringLiteralNode.getNext(); Set<String> phNames = Sets.newHashSet(); if (objLitNode != null) { // Register the placeholder names if (!objLitNode.isObjectLit()) { throw new MalformedException("OBJLIT node expected", objLitNode); } for (Node aNode = objLitNode.getFirstChild(); aNode != null; aNode = aNode.getNext()) { if (!aNode.isStringKey()) { throw new MalformedException("STRING_KEY node expected as OBJLIT key", aNode); } String phName =

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> aNode.getString(); if (!isLowerCamelCaseWithNumericSuffixes(phName)) { throw new MalformedException( "Placeholder name not in lowerCamelCase: " + phName, aNode); } if (phNames.contains(phName)) { throw new MalformedException("Duplicate placeholder name: " + phName, aNode); } phNames.add(phName); } } // Check that all placeholders from the message text have appropriate objlit // values Set<String> usedPlaceholders = builder.getPlaceholders(); for (String phName : usedPlaceholders) { if (!phNames.contains(phName)) { throw new MalformedException( "Unrecognized message placeholder referenced: " + phName, objLitNode); } } // Check that objLiteral have only names that are present in the // message text for (String phName : phNames) { if (!usedPlaceholders.contains(phName)) { throw new MalformedException( "Unused message placeholder: " + phName, objLitNode); } } } /** * Appends the message parts in a JS message value extracted from the given * text node. * * @param builder the JS message builder to append parts to * @param node the node with string literal that contains the message text * @throws MalformedException if {@code value} contains a reference to * an unregistered placeholder */ private void parseMessageTextNode(Builder builder, Node node) throws MalformedException { String value = extractStringFromStringExprNode(node); while (true) { int phBegin = value.indexOf(PH_JS_PREFIX); if (phBegin < 0) { // Just a string literal builder.appendStringPart(value); return; } else { if (phBegin > 0) { // A string literal followed by a placeholder builder.appendStringPart(value.substring(0, phBegin)); } // A placeholder. Find where it ends int phEnd = value.indexOf(PH_JS_SUFFIX, phBegin); if (phEnd < 0) { throw new MalformedException( "Placeholder incorrectly formatted in: " + builder.getKey(), node); } String phName = value.substring(phBegin + PH_JS_PREFIX.length(), phEnd); builder.appendPlaceholderReference(phName); int nextPos = phEnd + PH_JS_SUFFIX.length(); if (nextPos < value.length()) { // Iterate on the rest of the message value value = value.substring(nextPos); } else { // The message is parsed return; } } } } /** Visit a call to goog.getMsgWithFallback. */ private void visitFallbackFunctionCall(NodeTraversal t, Node call) { // Check to make sure the function call looks like: // goog.getMsgWithFallback(MSG_1, MSG_2); if (call.getChildCount() != 3 || !call.getChildAtIndex(1).isName() || !call.getChildAtIndex(2).isName()) { compiler.report(t.makeError(call,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> BAD_FALLBACK_SYNTAX)); return; } Node firstArg = call.getChildAtIndex(1); JsMessage firstMessage = getTrackedMessage(t, firstArg.getString()); if (firstMessage == null) { compiler.report( t.makeError(firstArg, FALLBACK_ARG_ERROR, firstArg.getString())); return; } Node secondArg = firstArg.getNext(); JsMessage secondMessage = getTrackedMessage( t, call.getChildAtIndex(2).getString()); if (secondMessage == null) { compiler.report( t.makeError(secondArg, FALLBACK_ARG_ERROR, secondArg.getString())); return; } processMessageFallback(call, firstMessage, secondMessage); } /** * Processes found JS message. Several examples of "standard" processing * routines are: * <ol> * <li>extract all JS messages * <li>replace JS messages with localized versions for some specific language * <li>check that messages have correct syntax and present in localization * bundle * </ol> * * @param message the found message * @param definition the definition of the object and usually contains all * additional message information like message node/parent's node */ abstract void processJsMessage(JsMessage message, JsMessageDefinition definition); /** * Processes the goog.getMsgWithFallback primitive. * goog.getMsgWithFallback(MSG_1, MSG_2); * * By default, does nothing. */ void processMessageFallback(Node callNode, JsMessage message1, JsMessage message2) {} /** * Returns whether the given JS identifier is a valid JS message name. */ boolean isMessageName(String identifier, boolean isNewStyleMessage) { return identifier.startsWith(MSG_PREFIX) && (style == JsMessage.Style.CLOSURE || isNewStyleMessage || !identifier.endsWith(DESC_SUFFIX)); } /** * Returns whether the given message name is in the unnamed namespace. */ private static boolean isUnnamedMessageName(String identifier) { return MSG_UNNAMED_PATTERN.matcher(identifier).matches(); } /** * Returns whether a string is nonempty, begins with a lowercase letter, and * contains only digits and underscores after the first underscore. */ static boolean isLowerCamelCaseWithNumericSuffixes(String input) { return CAMELCASE_PATTERN.matcher(input).matches(); } /** * Returns human-readable name of the given node's type. */ private static String getReadableTokenName(Node node) { return Token.name(node.getType()); } /** * Converts the given string from upper-underscore case to lower-camel case, * preserving numeric suffixes. For example: "NAME" -> "name" "A4_LETTER" -> * "a4Letter" "START_SPAN_1_23" -> "startSpan_1_23". */ static String toLowerCamelCaseWithNumericSuffixes(String input) { // Determine where the numeric suffixes begin int suffixStart = input.length(); while (suffixStart > 0) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> char ch = '\0'; int numberStart = suffixStart; while (numberStart > 0) { ch = input.charAt(numberStart - 1); if (Character.isDigit(ch)) { numberStart--; } else { break; } } if ((numberStart > 0) && (numberStart < suffixStart) && (ch == '_')) { suffixStart = numberStart - 1; } else { break; } } if (suffixStart == input.length()) { return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, input); } else { return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, input.substring(0, suffixStart)) + input.substring(suffixStart); } } /** * Checks a node's type. * * @throws MalformedException if the node is null or the wrong type */ protected void checkNode(@Nullable Node node, int type) throws MalformedException { if (node == null) { throw new MalformedException( "Expected node type " + type + "; found: null", node); } if (node.getType() != type) { throw new MalformedException( "Expected node type " + type + "; found: " + node.getType(), node); } } static class MalformedException extends Exception { private static final long serialVersionUID = 1L; private final Node node; MalformedException(String message, Node node) { super(message); this.node = node; } Node getNode() { return node; } } private static class MessageLocation { private final JsMessage message; private final Node messageNode; private MessageLocation(JsMessage message, Node messageNode) { this.message = message; this.messageNode = messageNode; } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> } } /** * Invalidates the given type, so that no properties on it will be renamed. */ private void addInvalidatingType(JSType type, JSError error) { type = type.restrictByNotNullOrUndefined(); if (type.isUnionType()) { for (JSType alt : type.toMaybeUnionType().getAlternates()) { addInvalidatingType(alt, error); } } else if (type.isEnumElementType()) { addInvalidatingType( type.toMaybeEnumElementType().getPrimitiveType(), error); } else { typeSystem.addInvalidatingType(type); recordInvalidationError(type, error); ObjectType objType = ObjectType.cast(type); if (objType != null && objType.getImplicitPrototype() != null) { typeSystem.addInvalidatingType(objType.getImplicitPrototype()); recordInvalidationError(objType.getImplicitPrototype(), error); } } } /** Returns the property for the given name, creating it if necessary. */ protected Property getProperty(String name) { if (!properties.containsKey(name)) { properties.put(name, new Property(name)); } return properties.get(name); } /** Public for testing. */ T getTypeWithProperty(String field, T type) { return typeSystem.getTypeWithProperty(field, type); } /** Tracks the current type system scope while traversing. */ private abstract class AbstractScopingCallback implements ScopedCallback { protected final Stack<StaticScope<T>> scopes = new Stack<StaticScope<T>>(); @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } @Override public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { scopes.push(typeSystem.getRootScope()); } else { scopes.push(typeSystem.getFunctionScope(t.getScopeRoot())); } } @Override public void exitScope(NodeTraversal t) { scopes.pop(); } /** Returns the current scope at this point in the file. */ protected StaticScope<T> getScope() { return scopes.peek(); } } /** * Finds all properties defined in the externs file and sets them as * ineligible for renaming from the type on which they are defined. */ private class FindExternProperties extends AbstractScopingCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { // TODO(johnlenz): Support object-literal property definitions. if (n.isGetProp()) { String field = n.getLastChild().getString(); T type = typeSystem.getType(getScope(), n.getFirstChild(), field); Property prop = getProperty(field); if (typeSystem.isInvalidatingType(type)) { prop.invalidate(); } else { prop.addTypeToSkip(type); // If this is a prototype property, then we want to skip assignments // to the instance type as well. These assignments are not usually // seen in the extern code itself, so we must handle them here. if ((type = typeSystem.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>getInstanceFromPrototype(type)) != null) { prop.getTypes().add(type); prop.typesToSkip.add(type); } } } } } /** * Traverses the tree, building a map from field names to Nodes for all * fields that can be renamed. */ private class FindRenameableProperties extends AbstractScopingCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isGetProp()) { handleGetProp(t, n); } else if (n.isObjectLit()) { handleObjectLit(t, n); } } /** * Processes a GETPROP node. */ private void handleGetProp(NodeTraversal t, Node n) { String name = n.getLastChild().getString(); T type = typeSystem.getType(getScope(), n.getFirstChild(), name); Property prop = getProperty(name); if (!prop.scheduleRenaming(n.getLastChild(), processProperty(t, prop, type, null))) { if (propertiesToErrorFor.containsKey(name)) { String suggestion = ""; if (type instanceof JSType) { JSType jsType = (JSType) type; if (jsType.isAllType() || jsType.isUnknownType()) { if (n.getFirstChild().isThis()) { suggestion = "The \"this\" object is unknown in the function," + "consider using @this"; } else { String qName = n.getFirstChild().getQualifiedName(); suggestion = "Consider casting " + qName + " if you know it's type."; } } else { List<String> errors = Lists.newArrayList(); printErrorLocations(errors, jsType); if (!errors.isEmpty()) { suggestion = "Consider fixing errors for the following types:\n"; suggestion += Joiner.on("\n").join(errors); } } } compiler.report(JSError.make( t.getSourceName(), n, propertiesToErrorFor.get(name), Warnings.INVALIDATION, name, (type == null ? "null" : type.toString()), n.toString(), suggestion)); } } } /** * Processes a OBJECTLIT node. */ private void handleObjectLit(NodeTraversal t, Node n) { Node child = n.getFirstChild(); while (child != null) { // Maybe STRING, GET, SET // We should never see a mix of numbers and strings. String name = child.getString(); T type = typeSystem.getType(getScope(), n, name); Property prop = getProperty(name); if (!prop.scheduleRenaming(child, processProperty(t, prop, type, null))) { // TODO(user): It doesn't look like the user can do much in this // case right now. if (propertiesToErrorFor.containsKey(name)) { compiler.report(JSError.make( t.getSourceName(), child, propertiesToErrorFor.get(name), Warnings.INVALIDATION, name, (type == null ? "null" : type.toString()), n.toString(), "")); } } child =

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> the case of an unknown JSType, we might need to add in the native * types since we don't track them, but only if they have the given * property. */ T getType(StaticScope<T> scope, Node node, String prop); /** * Returns true if a field reference on this type will invalidate all * references to that field as candidates for renaming. This is true if the * type is unknown or all-inclusive, as variables with such a type could be * references to any object. */ boolean isInvalidatingType(T type); /** * Informs the given type system that a type is invalidating due to a type * mismatch found during type checking. */ void addInvalidatingType(JSType type); /** * Returns a set of types that should be skipped given the given type. * This is necessary for interfaces when using JSTypes, as all super * interfaces must also be skipped. */ ImmutableSet<T> getTypesToSkipForType(T type); /** * Determines whether the given type is one whose properties should not be * considered for renaming. */ boolean isTypeToSkip(T type); /** Remove null and undefined from the options in the given type. */ T restrictByNotNullOrUndefined(T type); /** * Returns the alternatives if this is a type that represents multiple * types, and null if not. Union and interface types can correspond to * multiple other types. */ Iterable<T> getTypeAlternatives(T type); /** * Returns the type in the chain from the given type that contains the given * field or null if it is not found anywhere. */ T getTypeWithProperty(String field, T type); /** * Returns the type of the instance of which this is the prototype or null * if this is not a function prototype. */ T getInstanceFromPrototype(T type); /** * Records that this property could be referenced from any interface that * this type, or any type in its superclass chain, implements. */ void recordInterfaces(T type, T relatedType, DisambiguateProperties<T>.Property p); } /** Implementation of TypeSystem using JSTypes. */ private static class JSTypeSystem implements TypeSystem<JSType> { private final Set<JSType> invalidatingTypes; private JSTypeRegistry registry; public JSTypeSystem(AbstractCompiler compiler) { registry = compiler.getTypeRegistry(); invalidatingTypes = Sets.newHashSet( registry.getNativeType(JSTypeNative.ALL_TYPE), registry.getNativeType(JSTypeNative.NO_OBJECT_TYPE), registry.getNativeType(JSTypeNative.NO_TYPE), registry.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE), registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE), registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE), registry.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE), registry.getNativeType(JSTypeNative.UNKNOWN_TYPE)); } @Override public void addInvalidatingType(JSType type) { checkState(!type.isUnionType()); invalidatingTypes.add(type);

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> } @Override public StaticScope<JSType> getRootScope() { return null; } @Override public StaticScope<JSType> getFunctionScope(Node node) { return null; } @Override public JSType getType( StaticScope<JSType> scope, Node node, String prop) { if (node.getJSType() == null) { return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); } return node.getJSType(); } @Override public boolean isInvalidatingType(JSType type) { if (type == null || invalidatingTypes.contains(type) || type.isUnknownType() /* unresolved types */) { return true; } ObjectType objType = ObjectType.cast(type); return objType != null && !objType.hasReferenceName(); } @Override public ImmutableSet<JSType> getTypesToSkipForType(JSType type) { type = type.restrictByNotNullOrUndefined(); if (type.isUnionType()) { Set<JSType> types = Sets.newHashSet(type); for (JSType alt : type.toMaybeUnionType().getAlternates()) { types.addAll(getTypesToSkipForTypeNonUnion(alt)); } return ImmutableSet.copyOf(types); } else if (type.isEnumElementType()) { return getTypesToSkipForType( type.toMaybeEnumElementType().getPrimitiveType()); } return ImmutableSet.copyOf(getTypesToSkipForTypeNonUnion(type)); } private Set<JSType> getTypesToSkipForTypeNonUnion(JSType type) { Set<JSType> types = Sets.newHashSet(); JSType skipType = type; while (skipType != null) { types.add(skipType); ObjectType objSkipType = skipType.toObjectType(); if (objSkipType != null) { skipType = objSkipType.getImplicitPrototype(); } else { break; } } return types; } @Override public boolean isTypeToSkip(JSType type) { return type.isEnumType() || (type.autoboxesTo() != null); } @Override public JSType restrictByNotNullOrUndefined(JSType type) { return type.restrictByNotNullOrUndefined(); } @Override public Iterable<JSType> getTypeAlternatives(JSType type) { if (type.isUnionType()) { return type.toMaybeUnionType().getAlternates(); } else { ObjectType objType = type.toObjectType(); if (objType != null && objType.getConstructor() != null && objType.getConstructor().isInterface()) { List<JSType> list = Lists.newArrayList(); for (FunctionType impl : registry.getDirectImplementors(objType)) { list.add(impl.getInstanceType()); } return list; } else { return null; } } } @Override public ObjectType getTypeWithProperty(String field, JSType type) { if (type == null) { return null; } if (type.isEnumElementType()) { return getTypeWithProperty( field, type.toMaybeEnumElementType().getPrimitiveType()); }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> if (!(type instanceof ObjectType)) { if (type.autoboxesTo() != null) { type = type.autoboxesTo(); } else { return null; } } // Ignore the prototype itself at all times. if ("prototype".equals(field)) { return null; } // We look up the prototype chain to find the highest place (if any) that // this appears. This will make references to overridden properties look // like references to the initial property, so they are renamed alike. ObjectType foundType = null; ObjectType objType = ObjectType.cast(type); if (objType != null && objType.getConstructor() != null && objType.getConstructor().isInterface()) { ObjectType topInterface = FunctionType.getTopDefiningInterface( objType, field); if (topInterface != null && topInterface.getConstructor() != null) { foundType = topInterface.getConstructor().getPrototype(); } } else { while (objType != null && objType.getImplicitPrototype() != objType) { if (objType.hasOwnProperty(field)) { foundType = objType; } objType = objType.getImplicitPrototype(); } } // If the property does not exist on the referenced type but the original // type is an object type, see if any subtype has the property. if (foundType == null) { ObjectType maybeType = ObjectType.cast( registry.getGreatestSubtypeWithProperty(type, field)); // getGreatestSubtypeWithProperty does not guarantee that the property // is defined on the returned type, it just indicates that it might be, // so we have to double check. if (maybeType != null && maybeType.hasOwnProperty(field)) { foundType = maybeType; } } return foundType; } @Override public JSType getInstanceFromPrototype(JSType type) { if (type.isFunctionPrototypeType()) { ObjectType prototype = (ObjectType) type; FunctionType owner = prototype.getOwnerFunction(); if (owner.isConstructor() || owner.isInterface()) { return prototype.getOwnerFunction().getInstanceType(); } } return null; } @Override public void recordInterfaces(JSType type, JSType relatedType, DisambiguateProperties<JSType>.Property p) { ObjectType objType = ObjectType.cast(type); if (objType != null) { FunctionType constructor; if (objType.isFunctionType()) { constructor = objType.toMaybeFunctionType(); } else if (objType.isFunctionPrototypeType()) { constructor = objType.getOwnerFunction(); } else { constructor = objType.getConstructor(); } while (constructor != null) { for (ObjectType itype : constructor.getImplementedInterfaces()) { JSType top = getTypeWithProperty(p.name, itype); if (top != null) { p.addType(itype, top, relatedType); } else { recordInterfaces(itype, relatedType, p); } // If this interface invalidated this property, return now. if (p.skipRenaming) { return; } } if (constructor.isInterface() || constructor.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>isConstructor()) { constructor = constructor.getSuperClassConstructor(); } else { constructor = null; } } } } } /** Implementation of TypeSystem using concrete types. */ private static class ConcreteTypeSystem implements TypeSystem<ConcreteType> { private final TightenTypes tt; private int nextUniqueId; private CodingConvention codingConvention; private final Set<JSType> invalidatingTypes = Sets.newHashSet(); // An array of native types that are not tracked by type tightening, and // thus need to be added in if an unknown type is encountered. private static final JSTypeNative [] nativeTypes = new JSTypeNative[] { JSTypeNative.BOOLEAN_OBJECT_TYPE, JSTypeNative.NUMBER_OBJECT_TYPE, JSTypeNative.STRING_OBJECT_TYPE }; public ConcreteTypeSystem(TightenTypes tt, CodingConvention convention) { this.tt = tt; this.codingConvention = convention; } @Override public void addInvalidatingType(JSType type) { checkState(!type.isUnionType()); invalidatingTypes.add(type); } @Override public StaticScope<ConcreteType> getRootScope() { return tt.getTopScope(); } @Override public StaticScope<ConcreteType> getFunctionScope(Node decl) { ConcreteFunctionType func = tt.getConcreteFunction(decl); return (func != null) ? func.getScope() : (StaticScope<ConcreteType>) null; } @Override public ConcreteType getType( StaticScope<ConcreteType> scope, Node node, String prop) { if (scope != null) { ConcreteType c = tt.inferConcreteType( (TightenTypes.ConcreteScope) scope, node); return maybeAddAutoboxes(c, node, prop); } else { return null; } } /** * Add concrete types for autoboxing types if necessary. The concrete type * system does not track native types, like string, so add them if they are * present in the JSType for the node. */ private ConcreteType maybeAddAutoboxes( ConcreteType cType, Node node, String prop) { JSType jsType = node.getJSType(); if (jsType == null) { return cType; } else if (jsType.isUnknownType()) { for (JSTypeNative nativeType : nativeTypes) { ConcreteType concrete = tt.getConcreteInstance( tt.getTypeRegistry().getNativeObjectType(nativeType)); if (concrete != null && !concrete.getPropertyType(prop).isNone()) { cType = cType.unionWith(concrete); } } return cType; } return maybeAddAutoboxes(cType, jsType, prop); } private ConcreteType maybeAddAutoboxes( ConcreteType cType, JSType jsType, String prop) { jsType = jsType.restrictByNotNullOrUndefined(); if (jsType.isUnionType()) { for (JSType alt : jsType.toMaybeUnionType().getAlternates()) { cType = maybeAddAutoboxes(cType, alt, prop); } return cType; } else if (jsType.isEnum

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Check for usage of 'with'. * */ class ControlStructureCheck implements HotSwapCompilerPass { private final AbstractCompiler compiler; static final DiagnosticType USE_OF_WITH = DiagnosticType.warning( "JSC_USE_OF_WITH", "The use of the 'with' structure should be avoided."); ControlStructureCheck(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { check(root); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { check(scriptRoot); } /** * Reports errors for any invalid use of control structures. * * @param node Current node to check. */ private void check(Node node) { switch (node.getType()) { case Token.WITH: JSDocInfo info = node.getJSDocInfo(); boolean allowWith = info != null && info.getSuppressions().contains("with"); if (!allowWith) { report(node, USE_OF_WITH); } break; } for (Node bChild = node.getFirstChild(); bChild != null;) { Node next = bChild.getNext(); check(bChild); bChild = next; } } private void report(Node n, DiagnosticType error) { compiler.report(JSError.make(n.getSourceFileName(), n, error)); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { this.referencedObjType = null; } } @Override public String getReferenceName() { return referencedObjType == null ? "" : referencedObjType.getReferenceName(); } @Override public boolean hasReferenceName() { return referencedObjType == null ? null : referencedObjType.hasReferenceName(); } @Override public boolean matchesNumberContext() { return referencedType.matchesNumberContext(); } @Override public boolean matchesStringContext() { return referencedType.matchesStringContext(); } @Override public boolean matchesObjectContext() { return referencedType.matchesObjectContext(); } @Override public boolean canBeCalled() { return referencedType.canBeCalled(); } @Override public boolean isNoType() { return referencedType.isNoType(); } @Override public boolean isNoObjectType() { return referencedType.isNoObjectType(); } @Override public boolean isNoResolvedType() { return referencedType.isNoResolvedType(); } @Override public boolean isUnknownType() { return referencedType.isUnknownType(); } @Override public boolean isCheckedUnknownType() { return referencedType.isCheckedUnknownType(); } @Override public boolean isNullable() { return referencedType.isNullable(); } @Override public EnumType toMaybeEnumType() { return referencedType.toMaybeEnumType(); } @Override public boolean isConstructor() { return referencedType.isConstructor(); } @Override public boolean isNominalType() { return referencedType.isNominalType(); } @Override public boolean isInstanceType() { return referencedType.isInstanceType(); } @Override public boolean isInterface() { return referencedType.isInterface(); } @Override public boolean isOrdinaryFunction() { return referencedType.isOrdinaryFunction(); } @Override public boolean isAllType() { return referencedType.isAllType(); } @Override public boolean isStruct() { return referencedType.isStruct(); } @Override public boolean isDict() { return referencedType.isDict(); } @Override public boolean isNativeObjectType() { return referencedObjType == null ? false : referencedObjType.isNativeObjectType(); } @Override RecordType toMaybeRecordType() { return referencedType.toMaybeRecordType(); } @Override public UnionType toMaybeUnionType() { return referencedType.toMaybeUnionType(); } @Override public FunctionType toMaybeFunctionType() { return referencedType.toMaybeFunctionType(); } @Override public EnumElementType toMaybeEnumElementType() { return referencedType.toMaybeEnumElementType(); } @Override public TernaryValue testForEquality(JSType that) { return referencedType.testForEquality(that); } @Override public boolean isSubtype(JSType that) { return referencedType.isSubtype(that); } @Override public FunctionType getOwnerFunction() { return referencedObjType == null ? null : referencedObjType.getOwnerFunction(); } @Override public Iterable<ObjectType> getCtorImplementedInterfaces() { return referencedObjType == null ?

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> Collections.<ObjectType>emptyList() : referencedObjType.getCtorImplementedInterfaces(); } @Override public int hashCode() { return referencedType.hashCode(); } @Override String toStringHelper(boolean forAnnotations) { return referencedType.toStringHelper(forAnnotations); } @Override public ObjectType getImplicitPrototype() { return referencedObjType == null ? null : referencedObjType.getImplicitPrototype(); } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode) { return referencedObjType == null ? true : referencedObjType.defineProperty( propertyName, type, inferred, propertyNode); } @Override public boolean removeProperty(String name) { return referencedObjType == null ? false : referencedObjType.removeProperty(name); } @Override public JSType findPropertyType(String propertyName) { return referencedType.findPropertyType(propertyName); } @Override public JSDocInfo getJSDocInfo() { return referencedType.getJSDocInfo(); } @Override public void setJSDocInfo(JSDocInfo info) { if (referencedObjType != null) { referencedObjType.setJSDocInfo(info); } } @Override public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) { if (referencedObjType != null) { referencedObjType.setPropertyJSDocInfo(propertyName, info); } } @Override public FunctionType getConstructor() { return referencedObjType == null ? null : referencedObjType.getConstructor(); } @Override public ImmutableList<JSType> getTemplateTypes() { return referencedObjType == null ? null : referencedObjType.getTemplateTypes(); } @Override public <T> T visit(Visitor<T> visitor) { return referencedType.visit(visitor); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return referencedType.visit(visitor, that); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setReferencedType(referencedType.resolve(t, scope)); return this; } @Override public String toDebugHashCodeString() { return "{proxy:" + referencedType.toDebugHashCodeString() + "}"; } @Override public JSType getTypeOfThis() { if (referencedObjType != null) { return referencedObjType.getTypeOfThis(); } return super.getTypeOfThis(); } @Override public JSType collapseUnion() { if (referencedType.isUnionType()) { return referencedType.collapseUnion(); } return this; } @Override public void matchConstraint(JSType constraint) { referencedType.matchConstraint(constraint); } @Override public TemplatizedType toMaybeTemplatizedType() { return referencedType.toMaybeTemplatizedType(); } @Override public TemplateType toMaybeTemplateType() { return referencedType.toMaybeTemplateType(); } @Override public boolean hasAnyTemplateTypesInternal() { return referencedType.hasAnyTemplateTypes(); } @Override public TemplateTypeMap getTemplateTypeMap() {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> /** * Gets the type of the class that "owns" a method, or null if * we know that its un-owned. */ private JSType getClassOfMethod(Node n, Node parent) { if (parent.isAssign()) { Node lValue = parent.getFirstChild(); if (NodeUtil.isGet(lValue)) { // We have an assignment of the form "a.b = ...". JSType lValueType = lValue.getJSType(); if (lValueType != null && lValueType.isNominalConstructor()) { // If a.b is a constructor, then everything in this function // belongs to the "a.b" type. return (lValueType.toMaybeFunctionType()).getInstanceType(); } else { // If a.b is not a constructor, then treat this as a method // of whatever type is on "a". return normalizeClassType(lValue.getFirstChild().getJSType()); } } else { // We have an assignment of the form "a = ...", so pull the // type off the "a". return normalizeClassType(lValue.getJSType()); } } else if (NodeUtil.isFunctionDeclaration(n) || parent.isName()) { return normalizeClassType(n.getJSType()); } return null; } /** * Normalize the type of a constructor, its instance, and its prototype * all down to the same type (the instance type). */ private JSType normalizeClassType(JSType type) { if (type == null || type.isUnknownType()) { return type; } else if (type.isNominalConstructor()) { return (type.toMaybeFunctionType()).getInstanceType(); } else if (type.isFunctionPrototypeType()) { FunctionType owner = ((ObjectType) type).getOwnerFunction(); if (owner.isConstructor()) { return owner.getInstanceType(); } } return type; } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: checkNameDeprecation(t, n, parent); checkNameVisibility(t, n, parent); break; case Token.GETPROP: checkPropertyDeprecation(t, n, parent); checkPropertyVisibility(t, n, parent); checkConstantProperty(t, n); break; case Token.NEW: checkConstructorDeprecation(t, n, parent); break; case Token.FUNCTION: checkFinalClassOverrides(t, n, parent); break; } } /** * Checks the given NEW node to ensure that access restrictions are obeyed. */ private void checkConstructorDeprecation(NodeTraversal t, Node n, Node parent) { JSType type = n.getJSType(); if (type != null) { String deprecationInfo = getTypeDeprecationInfo(type); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> DEPRECATED_CLASS_REASON, type.toString(), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_CLASS, type.toString())); } } } } /** * Checks the given NAME node to ensure that access restrictions are obeyed. */ private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking definitions or constructors. if (parent.isFunction() || parent.isVar() || parent.isNew()) { return; } Scope.Var var = t.getScope().getVar(n.getString()); JSDocInfo docInfo = var == null ? null : var.getJSDocInfo(); if (docInfo != null && docInfo.isDeprecated() && shouldEmitDeprecationWarning(t, n, parent)) { if (docInfo.getDeprecationReason() != null) { compiler.report( t.makeError(n, DEPRECATED_NAME_REASON, n.getString(), docInfo.getDeprecationReason())); } else { compiler.report( t.makeError(n, DEPRECATED_NAME, n.getString())); } } } /** * Checks the given GETPROP node to ensure that access restrictions are * obeyed. */ private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking constructors. if (parent.isNew()) { return; } ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); String propertyName = n.getLastChild().getString(); if (objectType != null) { String deprecationInfo = getPropertyDeprecationInfo(objectType, propertyName); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_PROP_REASON, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_PROP, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true))); } } } } /** * Determines whether the given name is visible in the current context. * @param t The current traversal. * @param name The name node. */ private void checkNameVisibility(NodeTraversal t, Node name, Node parent) { Var var = t.getScope().getVar(name.getString()); if (var != null) { JSDocInfo docInfo = var.getJSDocInfo(); if (docInfo != null) { // If a name is private, make sure that we're in the same file. Visibility visibility = docInfo.getVisibility(); if (visibility == Visibility.PRIVATE) { StaticSourceFile varSrc = var.getSourceFile(); StaticSourceFile refSrc = name.getStaticSourceFile(); if (varSrc != null && refSrc != null && !varSrc.getName().equals(refSrc.getName())) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent))

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { return; } compiler.report( t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS, name.getString(), varSrc.getName())); } } } } } /** * Checks if a constructor is trying to override a final class. */ private void checkFinalClassOverrides(NodeTraversal t, Node fn, Node parent) { JSType type = fn.getJSType().toMaybeFunctionType(); if (type != null && type.isConstructor()) { JSType finalParentClass = getFinalParentClass(getClassOfMethod(fn, parent)); if (finalParentClass != null) { compiler.report( t.makeError(fn, EXTEND_FINAL_CLASS, type.getDisplayName(), finalParentClass.getDisplayName())); } } } /** * Determines whether the given property with @const tag got reassigned * @param t The current traversal. * @param getprop The getprop node. */ private void checkConstantProperty(NodeTraversal t, Node getprop) { // Check whether the property is modified Node parent = getprop.getParent(); boolean isDelete = parent.isDelProp(); if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop) && !parent.isInc() && !parent.isDec() && !isDelete) { return; } ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName); // Check whether constant properties are reassigned if (isConstant) { if (isDelete) { compiler.report( t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName)); return; } ObjectType oType = objectType; while (oType != null) { if (oType.hasReferenceName()) { if (initializedConstantProperties.containsEntry( oType.getReferenceName(), propertyName)) { compiler.report( t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE, propertyName)); break; } } oType = oType.getImplicitPrototype(); } Preconditions.checkState(objectType.hasReferenceName()); initializedConstantProperties.put(objectType.getReferenceName(), propertyName); // Add the prototype when we're looking at an instance object if (objectType.isInstanceType()) { ObjectType prototype = objectType.getImplicitPrototype(); if (prototype != null) { if (prototype.hasProperty(propertyName) && prototype.hasReferenceName()) { initializedConstantProperties.put(prototype.getReferenceName(), propertyName); } } } } } /** * Determines whether the given property is visible in the current context. * @param t The current traversal. * @param getprop The getprop node. */ private void checkPropertyVisibility(NodeTraversal t, Node getprop, Node parent) { ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); if (objectType !=

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> null) { // Is this a normal property access, or are we trying to override // an existing property? boolean isOverride = parent.getJSDocInfo() != null && parent.isAssign() && parent.getFirstChild() == getprop; // Find the lowest property defined on a class with visibility // information. if (isOverride) { objectType = objectType.getImplicitPrototype(); } JSDocInfo docInfo = null; for (; objectType != null; objectType = objectType.getImplicitPrototype()) { docInfo = objectType.getOwnPropertyJSDocInfo(propertyName); if (docInfo != null && docInfo.getVisibility() != Visibility.INHERITED) { break; } } if (objectType == null) { // We couldn't find a visibility modifier; assume it's public. return; } String referenceSource = getprop.getSourceFileName(); String definingSource = docInfo.getSourceName(); boolean sameInput = referenceSource != null && referenceSource.equals(definingSource); Visibility visibility = docInfo.getVisibility(); JSType ownerType = normalizeClassType(objectType); if (isOverride) { // Check an ASSIGN statement that's trying to override a property // on a superclass. JSDocInfo overridingInfo = parent.getJSDocInfo(); Visibility overridingVisibility = overridingInfo == null ? Visibility.INHERITED : overridingInfo.getVisibility(); // Check that (a) the property *can* be overridden, and // (b) that the visibility of the override is the same as the // visibility of the original property. if (visibility == Visibility.PRIVATE && !sameInput) { compiler.report( t.makeError(getprop, PRIVATE_OVERRIDE, objectType.toString())); } else if (overridingVisibility != Visibility.INHERITED && overridingVisibility != visibility) { compiler.report( t.makeError(getprop, VISIBILITY_MISMATCH, visibility.name(), objectType.toString(), overridingVisibility.name())); } } else { if (sameInput) { // private access is always allowed in the same file. return; } else if (visibility == Visibility.PRIVATE && (currentClass == null || !ownerType.isEquivalentTo(currentClass))) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } // private access is not allowed outside the file from a different // enclosing class. compiler.report( t.makeError(getprop, BAD_PRIVATE_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } else if (visibility == Visibility.PROTECTED) { // There are 3 types of legal accesses of a protected property: // 1) Accesses in the same file // 2) Overriding the property in a subclass // 3) Accessing the property from inside a subclass // The first two have already been checked for. if (currentClass == null || !currentClass.isSubtype(ownerType)) { compiler.report( t.makeError(getprop, BAD_PROTECTED

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> of the class with the singleton getter. By default, always * returns null. Meant to be overridden by subclasses. * * addSingletonGetter needs a coding convention because in the general case, * it can't be inlined. The function inliner sees that it creates an alias * to the given class in an inner closure, and bails out. * * @param callNode A CALL node. */ public String getSingletonGetterClassName(Node callNode); /** * In many JS libraries, the function that adds a singleton getter to a class * adds properties to the class. */ public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType); /** * @return Whether the function is inlinable by convention. */ public boolean isInlinableFunction(Node n); /** * @return the delegate relationship created by the call or null. */ public DelegateRelationship getDelegateRelationship(Node callNode); /** * In many JS libraries, the function that creates a delegate relationship * also adds properties to the delegator and delegate base. */ public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate); /** * @return the name of the delegate superclass. */ public String getDelegateSuperclassName(); /** * Checks for function calls that set the calling conventions on delegate * methods. */ public void checkForCallingConventionDefiningCalls( Node n, Map<String, String> delegateCallingConventions); /** * Defines the delegate proxy prototype properties. Their types depend on * properties of the delegate base methods. * * @param delegateProxyPrototypes List of delegate proxy prototypes. */ public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes, Map<String, String> delegateCallingConventions); /** * Gets the name of the global object. */ public String getGlobalObject(); /** * A Bind instance or null. */ public Bind describeFunctionBind(Node n); /** * A Bind instance or null. * @param useTypeInfo If we believe type information is reliable enough * to use to figure out what the bind function is. */ public Bind describeFunctionBind(Node n, boolean useTypeInfo); /** Bind class */ public static class Bind { // The target of the bind action final Node target; // The node representing the "this" value, maybe null final Node thisValue; // The head of a Node list representing the parameters final Node parameters; public Bind(Node target, Node thisValue, Node parameters) { this.target = target; this.thisValue = thisValue; this.parameters = parameters; } /** * The number of parameters bound (not including the 'this' value). */ int getBoundParameterCount() { if (parameters == null) { return 0; } Node paramParent = parameters.getParent(); return paramParent.getChildCount() - paramParent.getIndexOfChild(parameters); } } /** * Whether this CALL function is testing for the existence of a property.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Inequality(JSType that) { UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry); UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { TypePair p = element.getTypesUnderInequality(that); if (p.typeA != null) { thisRestricted.addAlternate(p.typeA); } if (p.typeB != null) { thatRestricted.addAlternate(p.typeB); } } return new TypePair( thisRestricted.build(), thatRestricted.build()); } @Override public TypePair getTypesUnderShallowInequality(JSType that) { UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry); UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { TypePair p = element.getTypesUnderShallowInequality(that); if (p.typeA != null) { thisRestricted.addAlternate(p.typeA); } if (p.typeB != null) { thatRestricted.addAlternate(p.typeB); } } return new TypePair( thisRestricted.build(), thatRestricted.build()); } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseUnionType(this); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseUnionType(this, that); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); // for circularly defined types. // Just resolve the alternates, but do not update as that breaks some error // reporting cases. for (JSType alternate : alternates) { alternate.resolve(t, scope); } // Ensure the union is in a normalized state. rebuildAlternates(); return this; } @Override public String toDebugHashCodeString() { List<String> hashCodes = Lists.newArrayList(); for (JSType a : alternates) { hashCodes.add(a.toDebugHashCodeString()); } return "{(" + Joiner.on(",").join(hashCodes) + ")}"; } @Override public boolean setValidator(Predicate<JSType> validator) { for (JSType a : alternates) { a.setValidator(validator); } return true; } @Override public JSType collapseUnion() { JSType currentValue = null; ObjectType currentCommonSuper = null; for (JSType a : alternates) { if (a.isUnknownType()) { return getNativeType(JSTypeNative.UNKNOWN_TYPE); } ObjectType obj = a.toObjectType(); if (obj == null) { if (currentValue == null && currentCommonSuper == null) { // If obj is not an object, then it must be a value. currentValue = a; } else { // Multiple values and objects will always collapse to the ALL_TYPE. return getNativeType(JSTypeNative.ALL_TYPE); } } else if

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> public boolean isSubtype(JSType that) { if (JSType.isSubtypeHelper(this, that)) { return true; } else { return that.isObject() && !that.isNoType() && !that.isNoResolvedType(); } } @Override public FunctionType toMaybeFunctionType() { return null; } @Override public boolean isNoObjectType() { return true; } @Override public ObjectType getImplicitPrototype() { return null; } @Override public String getReferenceName() { return null; } @Override public boolean matchesNumberContext() { return true; } @Override public boolean matchesObjectContext() { return true; } @Override public boolean matchesStringContext() { return true; } @Override public int hashCode() { return System.identityHashCode(this); } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode) { // nothing, all properties are defined return true; } @Override public boolean removeProperty(String name) { return false; } @Override public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) { // Do nothing, specific properties do not have JSDocInfo. } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseNoObjectType(); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseNoObjectType(that); } @Override String toStringHelper(boolean forAnnotations) { return forAnnotations ? "?" : "NoObject"; } @Override public FunctionType getConstructor() { return null; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { return this; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>ilineText, token); } builder.append(toString(token)); line = getRemainingJSDocLine(); if (option != WhitespaceOption.PRESERVE) { line = trimEnd(line); } builder.append(line); token = next(); } } while (true); } /** * Extracts the top-level block comment from the JsDoc comment, if any. * This method differs from the extractMultilineTextualBlock in that it * terminates under different conditions (it doesn't have the same * prechecks), it does not first read in the remaining of the current * line and its conditions for ignoring the "*" (STAR) are different. * * @param token The starting token. * * @return The extraction information. */ private ExtractionInfo extractBlockComment(JsDocToken token) { StringBuilder builder = new StringBuilder(); boolean ignoreStar = true; do { switch (token) { case ANNOTATION: case EOC: case EOF: return new ExtractionInfo(builder.toString().trim(), token); case STAR: if (!ignoreStar) { if (builder.length() > 0) { builder.append(' '); } builder.append('*'); } token = next(); continue; case EOL: ignoreStar = true; builder.append('\n'); token = next(); continue; default: if (!ignoreStar && builder.length() > 0) { builder.append(' '); } ignoreStar = false; builder.append(toString(token)); String line = getRemainingJSDocLine(); line = trimEnd(line); builder.append(line); token = next(); } } while (true); } /** * Trim characters from only the end of a string. * This method will remove all whitespace characters * (defined by Character.isWhitespace(char), in addition to the characters * provided, from the end of the provided string. * * @param s String to be trimmed * @return String with whitespace and characters in extraChars removed * from the end. */ private static String trimEnd(String s) { int trimCount = 0; while (trimCount < s.length()) { char ch = s.charAt(s.length() - trimCount - 1); if (Character.isWhitespace(ch)) { trimCount++; } else { break; } } if (trimCount == 0) { return s; } return s.substring(0, s.length() - trimCount); } // Based on ES4 grammar proposed on July 10, 2008. // http://wiki.ecmascript.org/doku.php?id=spec:spec // Deliberately written to line up with the actual grammar rules, // for maximum flexibility. // TODO(nicksantos): The current implementation tries to maintain backwards // compatibility with previous versions of the spec whenever we can. // We should try to gradually withdraw support for these. /** * TypeExpressionAnnotation := TypeExpression | * '{' TopLevelTypeExpression '}'

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.javascript.rhino.JSDocInfo; /** * The {@code StaticSlot} interface must be implemented by variables that can * appear as members of a {@code StaticScope}. * * @param <T> The type of information stored about the slot */ public interface StaticSlot<T> { /** * Gets the name of the slot. */ String getName(); /** * Returns the type information, if any, for this slot. * @return The type or {@code null} if no type is declared for it. */ T getType(); /** * Returns whether the type has been inferred (as opposed to declared). */ boolean isTypeInferred(); /** Gets the declaration of this symbol. May not exist. */ StaticReference<T> getDeclaration(); /** Gets the JSDoc for this slot. */ JSDocInfo getJSDocInfo(); }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * John Lenz * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; /** * A "can cast to" relationship visitor. */ class CanCastToVisitor implements RelationshipVisitor<Boolean> { @Override public Boolean caseUnknownType(JSType thisType, JSType thatType) { return true; } @Override public Boolean caseNoType(JSType thatType) { return true; } @Override public Boolean caseNoObjectType(JSType thatType) { return true; // TODO(johnlenz): restrict to objects } @Override public Boolean caseAllType(JSType thatType) { return true; } boolean canCastToUnion(JSType thisType, UnionType unionType) { for (JSType type : unionType.getAlternates()) { if (thisType.visit(this, type)) { return true; } } return false; } boolean canCastToFunction(JSType thisType, FunctionType functionType) { if (thisType.isFunctionType()) { // TODO(johnlenz): visit function parts return true; } else { return thisType.isSubtype(functionType) || functionType.isSubtype(thisType); } } private boolean isInterface(JSType type) { ObjectType objType = type.toObjectType(); if (objType != null) { JSType constructor = objType.getConstructor();

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> return constructor != null && constructor.isInterface(); } return false; } Boolean castCastToHelper(JSType thisType, JSType thatType) { if (thatType.isUnknownType() || thatType.isAllType() || thatType.isNoObjectType() // TODO(johnlenz): restrict to objects || thatType.isNoType()) { return true; } else if (thisType.isRecordType() || thatType.isRecordType()) { return true; // TODO(johnlenz): are there any misuses we can catch? } else if (isInterface(thisType) || isInterface(thatType)) { return true; // TODO(johnlenz): are there any misuses we can catch? } else if (thatType.isEnumElementType()) { return thisType.visit(this, thatType.toMaybeEnumElementType().getPrimitiveType()); } else if (thatType.isUnionType()) { return canCastToUnion(thisType, thatType.toMaybeUnionType()); } else if (thatType.isFunctionType()) { return canCastToFunction(thisType, thatType.toMaybeFunctionType()); } else if (thatType.isTemplatizedType()) { // TODO(johnlenz): once the templated type work is finished, // restrict the type parameters. return thisType.visit(this, thatType.toMaybeTemplatizedType().getReferencedTypeInternal()); } return thisType.isSubtype(thatType) || thatType.isSubtype(thisType); } @Override public Boolean caseValueType(ValueType thisType, JSType thatType) { return castCastToHelper(thisType, thatType); } @Override public Boolean caseObjectType(ObjectType thisType, JSType thatType) { return castCastToHelper(thisType, thatType); } @Override public Boolean caseFunctionType(FunctionType thisType, JSType thatType) { return castCastToHelper(thisType, thatType); } @Override public Boolean caseUnionType(UnionType thisType, JSType thatType) { boolean visited = false; for (JSType type : thisType.getAlternates()) { if (type.isVoidType() || type.isNullType()) { // Don't allow if the only match between the types is null or void, // otherwise any nullable type would be castable to any other nullable // type and we don't want that. } else { visited = true; if (type.visit(this, thatType)) { return true; } } } // Special case the "null|undefined" union and allow it to be cast // to any cast to any type containing allowing either null|undefined. if (!visited) { JSType NULL_TYPE = thisType.getNativeType(JSTypeNative.NULL_TYPE); JSType VOID_TYPE = thisType.getNativeType(JSTypeNative.VOID_TYPE); return NULL_TYPE.visit(this, thatType) || VOID_TYPE.visit(this, thatType); } return false; } @Override public Boolean caseTemplatizedType( TemplatizedType thisType,

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2012 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Checks for common errors, such as misplaced semicolons: * <pre> * if (x); act_now(); * </pre> * or comparison against NaN: * <pre> * if (x === NaN) act(); * </pre> * and generates warnings. * * @author johnlenz@google.com (John Lenz) */ final class CheckSuspiciousCode extends AbstractPostOrderCallback { static final DiagnosticType SUSPICIOUS_SEMICOLON = DiagnosticType.warning( "JSC_SUSPICIOUS_SEMICOLON", "If this if/for/while really shouldn't have a body, use {}"); static final DiagnosticType SUSPICIOUS_COMPARISON_WITH_NAN = DiagnosticType.warning( "JSC_SUSPICIOUS_NAN", "Comparison again NaN is always false. Did you mean isNaN()?"); CheckSuspiciousCode() { } @Override public void visit(NodeTraversal t, Node n, Node parent) { checkMissingSemicolon(t, n); checkNaN(t, n); } private void checkMissingSemicolon(NodeTraversal t, Node n) { switch (n.getType()) { case Token.IF: Node trueCase = n.getFirstChild().getNext(); reportIfWasEmpty(t, trueCase); Node elseCase = trueCase.getNext(); if (elseCase != null) { reportIfWasEmpty(t, elseCase); } break; case Token.WHILE: case Token.FOR: reportIfWasEmpty(t, NodeUtil.getLoopCodeBlock(n)); break; } } private void reportIfWasEmpty(NodeTraversal t, Node block) { Preconditions.checkState(block.isBlock()); // A semicolon is distinguished from a block without children by // annotating it with EMPTY_BLOCK. Blocks without children are // usually intentional, especially with loops. if (!block.hasChildren() && block.wasEmptyNode()) { t.getCompiler().report( t.makeError(block, SUSPICIOUS_SEMICOLON)); } } private void checkNaN(NodeTraversal t

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>, Node n) { switch (n.getType()) { case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LT: case Token.NE: case Token.SHEQ: case Token.SHNE: reportIfNaN(t, n.getFirstChild()); reportIfNaN(t, n.getLastChild()); } } private void reportIfNaN(NodeTraversal t, Node n) { if (NodeUtil.isNaN(n)) { t.getCompiler().report( t.makeError(n.getParent(), SUSPICIOUS_COMPARISON_WITH_NAN)); } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>, int lineno, int charno, CheckLevel level, DiagnosticType type, String... arguments) { return new JSError( sourceName, null, lineno, charno, type, level, arguments); } /** * Creates a JSError from a file and Node position. * * @param sourceName The source file name * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public static JSError make(String sourceName, Node n, DiagnosticType type, String... arguments) { return new JSError(sourceName, n, type, arguments); } /** * Creates a JSError from a file and Node position. * * @param n Determines the line and char position and source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public static JSError make(Node n, DiagnosticType type, String... arguments) { return new JSError(n.getSourceFileName(), n, type, arguments); } /** * Creates a JSError from a file and Node position. * * @param sourceName The source file name * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public static JSError make(String sourceName, Node n, CheckLevel level, DiagnosticType type, String... arguments) { return new JSError(sourceName, n, n.getLineno(), n.getCharno(), type, level, arguments); } // // JSError constructors // /** * Creates a JSError at a CheckLevel for a source file location. * Private to avoid any entanglement with code outside of the compiler. */ private JSError( String sourceName, @Nullable Node node, int lineno, int charno, DiagnosticType type, CheckLevel level, String... arguments) { this.type = type; this.node = node; this.description = type.format.format(arguments); this.lineNumber = lineno; this.charno = charno; this.sourceName = sourceName; this.defaultLevel = level == null ? type.level : level; this.level = level == null ? type.level : level; } /** * Creates a JSError for a source file location. Private to avoid * any entanglement with code outside of the compiler. */ private JSError(String sourceName, @Nullable Node node, DiagnosticType type, String... arguments) { this(sourceName, node, (node != null) ? node.getLineno() : -1, (node != null) ? node.getCharno() : -1, type, null, arguments); } public DiagnosticType getType() { return type; } /** * Format a message at the given level. * * @return the formatted message or {@code null} */ public String format(CheckLevel level, MessageFormatter formatter) { switch (level) { case ERROR: return formatter.formatError(this

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Convention convention; final JSTypeRegistry typeRegistry; private ChainableReverseAbstractInterpreter firstLink; private ChainableReverseAbstractInterpreter nextLink; /** * Constructs an interpreter, which is the only link in a chain. Interpreters * can be appended using {@link #append}. */ public ChainableReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { Preconditions.checkNotNull(convention); this.convention = convention; this.typeRegistry = typeRegistry; firstLink = this; nextLink = null; } /** * Appends a link to {@code this}, returning the updated last link. * <p> * The pattern {@code new X().append(new Y())...append(new Z())} forms a * chain starting with X, then Y, then ... Z. * @param lastLink a chainable interpreter, with no next link * @return the updated last link */ public ChainableReverseAbstractInterpreter append( ChainableReverseAbstractInterpreter lastLink) { Preconditions.checkArgument(lastLink.nextLink == null); this.nextLink = lastLink; lastLink.firstLink = this.firstLink; return lastLink; } /** * Gets the first link of this chain. */ public ChainableReverseAbstractInterpreter getFirst() { return firstLink; } /** * Calculates the preciser scope starting with the first link. */ protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return firstLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } /** * Delegates the calculation of the preciser scope to the next link. * If there is no next link, returns the blind scope. */ protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome) : blindScope; } /** * Returns the type of a node in the given scope if the node corresponds to a * name whose type is capable of being refined. * @return The current type of the node if it can be refined, null otherwise. */ protected JSType getTypeIfRefinable(Node node, FlowScope scope) { switch (node.getType()) { case Token.NAME: StaticSlot<JSType> nameVar = scope.getSlot(node.getString()); if (nameVar != null) { JSType nameVarType = nameVar.getType(); if (nameVarType == null) { nameVarType = node.getJSType(); } return nameVarType; } return null; case Token.GETPROP: String qualifiedName = node.getQualifiedName(); if (qualifiedName == null) { return null; } StaticSlot<JSType> propVar = scope.getSlot(qualifiedName); JSType propVarType = null; if (propVar != null) { propVarType = propVar.getType(); } if (propVarType == null) { propVarType = node.get

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>JSType(); } if (propVarType == null) { propVarType = getNativeType(UNKNOWN_TYPE); } return propVarType; } return null; } /** * Declares a refined type in {@code scope} for the name represented by * {@code node}. It must be possible to refine the type of the given node in * the given scope, as determined by {@link #getTypeIfRefinable}. */ protected void declareNameInScope(FlowScope scope, Node node, JSType type) { switch (node.getType()) { case Token.NAME: scope.inferSlotType(node.getString(), type); break; case Token.GETPROP: String qualifiedName = node.getQualifiedName(); Preconditions.checkNotNull(qualifiedName); JSType origType = node.getJSType(); origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType; scope.inferQualifiedSlot(node, qualifiedName, origType, type); break; case Token.THIS: // "this" references aren't currently modeled in the CFG. break; default: throw new IllegalArgumentException("Node cannot be refined. \n" + node.toStringTree()); } } /** * @see #getRestrictedWithoutUndefined(JSType) */ private final Visitor<JSType> restrictUndefinedVisitor = new Visitor<JSType>() { @Override public JSType caseEnumElementType(EnumElementType enumElementType) { JSType type = enumElementType.getPrimitiveType().visit(this); if (type != null && enumElementType.getPrimitiveType().isEquivalentTo(type)) { return enumElementType; } else { return type; } } @Override public JSType caseAllType() { return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE); } @Override public JSType caseNoObjectType() { return getNativeType(NO_OBJECT_TYPE); } @Override public JSType caseNoType() { return getNativeType(NO_TYPE); } @Override public JSType caseBooleanType() { return getNativeType(BOOLEAN_TYPE); } @Override public JSType caseFunctionType(FunctionType type) { return type; } @Override public JSType caseNullType() { return getNativeType(NULL_TYPE); } @Override public JSType caseNumberType() { return getNativeType(NUMBER_TYPE); } @Override public JSType caseObjectType(ObjectType type) { return type; } @Override public JSType caseStringType() { return getNativeType(STRING_TYPE); } @Override public JSType caseUnionType(UnionType type) { return type.getRestrictedUnion(getNativeType(VOID_TYPE)); } @Override public JSType caseUnknownType() { return getNativeType(UNKNOWN_TYPE); } @Override public JSType caseVoidType() { return null; } @Override public JSType caseTemplatizedType(TemplatizedType type) { return caseObjectType(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Map; /** * Filters warnings based on in-code {@code @suppress} annotations. * @author nicksantos@google.com (Nick Santos) */ class SuppressDocWarningsGuard extends WarningsGuard { private static final long serialVersionUID = 1L; /** Warnings guards for each suppressible warnings group, indexed by name. */ private final Map<String, DiagnosticGroupWarningsGuard> suppressors = Maps.newHashMap(); /** * The suppressible groups, indexed by name. */ SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) { for (Map.Entry<String, DiagnosticGroup> entry : suppressibleGroups.entrySet()) { suppressors.put( entry.getKey(), new DiagnosticGroupWarningsGuard( entry.getValue(), CheckLevel.OFF)); } } @Override public CheckLevel level(JSError error) { Node node = error.node; if (node != null) { boolean visitedFunction = false; for (Node current = node; current != null; current = current.getParent()) { int type = current.getType(); JSDocInfo info = null; if (type == Token.FUNCTION) { info = NodeUtil.getBestJSDocInfo(current); visitedFunction = true; } else if (type == Token.SCRIPT) { info = current.getJSDocInfo(); } else if (current.isVar() || current.isAssign()) { // There's one edge case we're worried about: // if the warning points to an assigment to a function, we // want the suppressions on that function to apply. // It's OK if we double-count some cases. Node rhs = NodeUtil.getRValueOfLValue(current.getFirstChild()); if (rhs != null) { if (rhs.isCast()) { rhs = rhs.getFirstChild(); } if (rhs.isFunction() && !visitedFunction) { info = NodeUtil.getBestJSDocInfo(current); } } } if (info != null) { for (String suppressor : info.getSuppressions()) { WarningsGuard guard = suppressors.get(suppressor);

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>SwitchStatement statementNode); abstract T processThrowStatement(ThrowStatement statementNode); abstract T processTryStatement(TryStatement statementNode); abstract T processUnaryExpression(UnaryExpression exprNode); abstract T processVariableDeclaration(VariableDeclaration declarationNode); abstract T processVariableInitializer(VariableInitializer initializerNode); abstract T processWhileLoop(WhileLoop loopNode); abstract T processWithStatement(WithStatement statementNode); abstract T processIllegalToken(AstNode node); public T process(AstNode node) { switch (node.getType()) { case Token.ADD: case Token.AND: case Token.BITAND: case Token.BITOR: case Token.BITXOR: case Token.COMMA: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.IN: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.OR: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return processInfixExpression((InfixExpression) node); case Token.ARRAYLIT: return processArrayLiteral((ArrayLiteral) node); case Token.ASSIGN: case Token.ASSIGN_ADD: case Token.ASSIGN_BITAND: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_DIV: case Token.ASSIGN_LSH: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_RSH: case Token.ASSIGN_SUB: case Token.ASSIGN_URSH: return processAssignment((Assignment) node); case Token.BITNOT: case Token.DEC: case Token.DELPROP: case Token.INC: case Token.NEG: case Token.NOT: case Token.POS: case Token.TYPEOF: case Token.VOID: return processUnaryExpression((UnaryExpression) node); case Token.BLOCK: if (node instanceof Block) { return processBlock((Block) node); } else if (node instanceof Scope) { return processScope((Scope) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.BREAK: return processBreakStatement((BreakStatement) node); case Token.CALL: return processFunctionCall((FunctionCall) node); case Token.CASE: case Token.DEFAULT: return processSwitchCase((SwitchCase) node); case Token.CATCH: return processCatchClause((CatchClause) node); case Token.COLON: return processObjectProperty((ObjectProperty) node); case Token.CONTINUE: return processContinueStatement((ContinueStatement) node); case Token.DO: return processDoLoop((DoLoop) node); case Token.EMPTY: return (node instanceof EmptyExpression) ? processEmptyExpression((EmptyExpression) node) : processEmptyStatement((EmptyStatement)

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> node); case Token.EXPR_RESULT: case Token.EXPR_VOID: if (node instanceof ExpressionStatement) { return processExpressionStatement((ExpressionStatement) node); } else if (node instanceof LabeledStatement) { return processLabeledStatement((LabeledStatement) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.DEBUGGER: case Token.FALSE: case Token.NULL: case Token.THIS: case Token.TRUE: return processKeywordLiteral((KeywordLiteral) node); case Token.FOR: if (node instanceof ForInLoop) { return processForInLoop((ForInLoop) node); } else if (node instanceof ForLoop) { return processForLoop((ForLoop) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.FUNCTION: return processFunctionNode((FunctionNode) node); case Token.GETELEM: return processElementGet((ElementGet) node); case Token.GETPROP: return processPropertyGet((PropertyGet) node); case Token.HOOK: return processConditionalExpression((ConditionalExpression) node); case Token.IF: return processIfStatement((IfStatement) node); case Token.LABEL: return processLabel((Label) node); case Token.LP: return processParenthesizedExpression((ParenthesizedExpression) node); case Token.NAME: return processName((Name) node); case Token.NEW: return processNewExpression((NewExpression) node); case Token.NUMBER: return processNumberLiteral((NumberLiteral) node); case Token.OBJECTLIT: return processObjectLiteral((ObjectLiteral) node); case Token.REGEXP: return processRegExpLiteral((RegExpLiteral) node); case Token.RETURN: return processReturnStatement((ReturnStatement) node); case Token.SCRIPT: return processAstRoot((AstRoot) node); case Token.STRING: return processStringLiteral((StringLiteral) node); case Token.SWITCH: return processSwitchStatement((SwitchStatement) node); case Token.THROW: return processThrowStatement((ThrowStatement) node); case Token.TRY: return processTryStatement((TryStatement) node); case Token.CONST: case Token.VAR: if (node instanceof VariableDeclaration) { return processVariableDeclaration((VariableDeclaration) node); } else if (node instanceof VariableInitializer) { return processVariableInitializer((VariableInitializer) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.WHILE: return processWhileLoop((WhileLoop) node); case Token.WITH: return processWithStatement((WithStatement) node); } return processIllegalToken(node); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.StaticSourceFile; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * NodeUtil contains generally useful AST utilities. * */ public final class NodeUtil { static final long MAX_POSITIVE_INTEGER_NUMBER = (long) Math.pow(2, 53); static final String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty"; static final char LARGEST_BASIC_LATIN = 0x7f; /** the set of builtin constructors that don't have side effects. */ private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS = new HashSet<String>(Arrays.asList( "Array", "Date", "Error", "Object", "RegExp", "XMLHttpRequest")); // Utility class; do not instantiate. private NodeUtil() {} /** * Gets the boolean value of a node that represents a expression. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function. * Note: unlike getBooleanValue this function does not return UNKNOWN * for expressions with side-effects. */ static TernaryValue getImpureBooleanValue(Node n) { switch (n.getType()) { case Token.ASSIGN: case Token.COMMA: // For ASSIGN and COMMA the value is the value of the RHS. return get

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>ImpureBooleanValue(n.getLastChild()); case Token.NOT: TernaryValue value = getImpureBooleanValue(n.getLastChild()); return value.not(); case Token.AND: { TernaryValue lhs = getImpureBooleanValue(n.getFirstChild()); TernaryValue rhs = getImpureBooleanValue(n.getLastChild()); return lhs.and(rhs); } case Token.OR: { TernaryValue lhs = getImpureBooleanValue(n.getFirstChild()); TernaryValue rhs = getImpureBooleanValue(n.getLastChild()); return lhs.or(rhs); } case Token.HOOK: { TernaryValue trueValue = getImpureBooleanValue( n.getFirstChild().getNext()); TernaryValue falseValue = getImpureBooleanValue(n.getLastChild()); if (trueValue.equals(falseValue)) { return trueValue; } else { return TernaryValue.UNKNOWN; } } case Token.ARRAYLIT: case Token.OBJECTLIT: // ignoring side-effects return TernaryValue.TRUE; case Token.VOID: return TernaryValue.FALSE; default: return getPureBooleanValue(n); } } /** * Gets the boolean value of a node that represents a literal. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function * except it return UNKNOWN for known values with side-effects, use * getImpureBooleanValue if you don't care about side-effects. */ static TernaryValue getPureBooleanValue(Node n) { switch (n.getType()) { case Token.STRING: return TernaryValue.forBoolean(n.getString().length() > 0); case Token.NUMBER: return TernaryValue.forBoolean(n.getDouble() != 0); case Token.NOT: return getPureBooleanValue(n.getLastChild()).not(); case Token.NULL: case Token.FALSE: return TernaryValue.FALSE; case Token.VOID: if (!mayHaveSideEffects(n.getFirstChild())) { return TernaryValue.FALSE; } break; case Token.NAME: String name = n.getString(); if ("undefined".equals(name) || "NaN".equals(name)) { // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return TernaryValue.FALSE; } else if ("Infinity".equals(name)) { return TernaryValue.TRUE; } break; case Token.TRUE: case Token.REGEXP: return TernaryValue.TRUE; case Token.ARRAYLIT: case Token.OBJECTLIT: if (!mayHaveSideEffects(n)) { return TernaryValue.TRUE; } break; } return TernaryValue.UNKNOWN; } /** * Gets the value of a node as a String, or null if it cannot be converted. * When it returns a non-null String, this method effectively emulates the * <code>String()</code> JavaScript cast function. */ static String getStringValue(Node n) { // TODO(user

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>): regex literals as well. switch (n.getType()) { case Token.STRING: case Token.STRING_KEY: return n.getString(); case Token.NAME: String name = n.getString(); if ("undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name)) { return name; } break; case Token.NUMBER: return getStringValue(n.getDouble()); case Token.FALSE: return "false"; case Token.TRUE: return "true"; case Token.NULL: return "null"; case Token.VOID: return "undefined"; case Token.NOT: TernaryValue child = getPureBooleanValue(n.getFirstChild()); if (child != TernaryValue.UNKNOWN) { return child.toBoolean(true) ? "false" : "true"; // reversed. } break; case Token.ARRAYLIT: return arrayToString(n); case Token.OBJECTLIT: return "[object Object]"; } return null; } static String getStringValue(double value) { long longValue = (long) value; // Return "1" instead of "1.0" if (longValue == value) { return Long.toString(longValue); } else { return Double.toString(value); } } /** * When converting arrays to string using Array.prototype.toString or * Array.prototype.join, the rules for conversion to String are different * than converting each element individually. Specifically, "null" and * "undefined" are converted to an empty string. * @param n A node that is a member of an Array. * @return The string representation. */ static String getArrayElementStringValue(Node n) { return (NodeUtil.isNullOrUndefined(n) || n.isEmpty()) ? "" : getStringValue(n); } static String arrayToString(Node literal) { Node first = literal.getFirstChild(); StringBuilder result = new StringBuilder(); for (Node n = first; n != null; n = n.getNext()) { String childValue = getArrayElementStringValue(n); if (childValue == null) { return null; } if (n != first) { result.append(','); } result.append(childValue); } return result.toString(); } /** * Gets the value of a node as a Number, or null if it cannot be converted. * When it returns a non-null Double, this method effectively emulates the * <code>Number()</code> JavaScript cast function. */ static Double getNumberValue(Node n) { switch (n.getType()) { case Token.TRUE: return 1.0; case Token.FALSE: case Token.NULL: return 0.0; case Token.NUMBER: return n.getDouble(); case Token.VOID: if (mayHaveSideEffects(n.getFirstChild())) { return null; } else { return Double.NaN; } case Token.NAME: // Check for known constants String name = n.getString(); if (name.equals("undefined")) { return Double.NaN;

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> } if (name.equals("NaN")) { return Double.NaN; } if (name.equals("Infinity")) { return Double.POSITIVE_INFINITY; } return null; case Token.NEG: if (n.getChildCount() == 1 && n.getFirstChild().isName() && n.getFirstChild().getString().equals("Infinity")) { return Double.NEGATIVE_INFINITY; } return null; case Token.NOT: TernaryValue child = getPureBooleanValue(n.getFirstChild()); if (child != TernaryValue.UNKNOWN) { return child.toBoolean(true) ? 0.0 : 1.0; // reversed. } break; case Token.STRING: return getStringNumberValue(n.getString()); case Token.ARRAYLIT: case Token.OBJECTLIT: String value = getStringValue(n); return value != null ? getStringNumberValue(value) : null; } return null; } static Double getStringNumberValue(String rawJsString) { if (rawJsString.contains("\u000b")) { // vertical tab is not always whitespace return null; } String s = trimJsWhiteSpace(rawJsString); // return ScriptRuntime.toNumber(s); if (s.length() == 0) { return 0.0; } if (s.length() > 2 && s.charAt(0) == '0' && (s.charAt(1) == 'x' || s.charAt(1) == 'X')) { // Attempt to convert hex numbers. try { return Double.valueOf(Integer.parseInt(s.substring(2), 16)); } catch (NumberFormatException e) { return Double.NaN; } } if (s.length() > 3 && (s.charAt(0) == '-' || s.charAt(0) == '+') && s.charAt(1) == '0' && (s.charAt(2) == 'x' || s.charAt(2) == 'X')) { // hex numbers with explicit signs vary between browsers. return null; } // Firefox and IE treat the "Infinity" differently. Firefox is case // insensitive, but IE treats "infinity" as NaN. So leave it alone. if (s.equals("infinity") || s.equals("-infinity") || s.equals("+infinity")) { return null; } try { return Double.parseDouble(s); } catch (NumberFormatException e) { return Double.NaN; } } static String trimJsWhiteSpace(String s) { int start = 0; int end = s.length(); while (end > 0 && isStrWhiteSpaceChar(s.charAt(end - 1)) == TernaryValue.TRUE) { end--; } while (start < end && isStrWhiteSpaceChar(s.charAt(start)) == TernaryValue.TRUE) { start++; } return s.substring(start, end); } /** * Copied from Rhino's ScriptRuntime */ public static TernaryValue isStrWhiteSpaceChar(int c)

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { switch (c) { case '\u000B': // <VT> return TernaryValue.UNKNOWN; // IE says "no", ECMAScript says "yes" case ' ': // <SP> case '\n': // <LF> case '\r': // <CR> case '\t': // <TAB> case '\u00A0': // <NBSP> case '\u000C': // <FF> case '\u2028': // <LS> case '\u2029': // <PS> case '\uFEFF': // <BOM> return TernaryValue.TRUE; default: return (Character.getType(c) == Character.SPACE_SEPARATOR) ? TernaryValue.TRUE : TernaryValue.FALSE; } } /** * Gets the function's name. This method recognizes five forms: * <ul> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * In two last cases with named function expressions, the second name is * returned (the variable of qualified name). * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getFunctionName(Node n) { Preconditions.checkState(n.isFunction()); Node parent = n.getParent(); switch (parent.getType()) { case Token.NAME: // var name = function() ... // var name2 = function name1() ... return parent.getQualifiedName(); case Token.ASSIGN: // qualified.name = function() ... // qualified.name2 = function name1() ... return parent.getFirstChild().getQualifiedName(); default: // function name() ... String name = n.getFirstChild().getQualifiedName(); return name; } } /** * Gets the function's name. This method recognizes the forms: * <ul> * <li>{@code &#123;'name': function() ...&#125;}</li> * <li>{@code &#123;name: function() ...&#125;}</li> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ public static String getNearestFunctionName(Node n) { if (!n.isFunction()) { return null; } String name = getFunctionName(n

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>); if (name != null) { return name; } // Check for the form { 'x' : function() { } } Node parent = n.getParent(); switch (parent.getType()) { case Token.SETTER_DEF: case Token.GETTER_DEF: case Token.STRING_KEY: // Return the name of the literal's key. return parent.getString(); case Token.NUMBER: return getStringValue(parent); } return null; } /** * Returns true if this is an immutable value. */ static boolean isImmutableValue(Node n) { switch (n.getType()) { case Token.STRING: case Token.NUMBER: case Token.NULL: case Token.TRUE: case Token.FALSE: return true; case Token.CAST: case Token.NOT: return isImmutableValue(n.getFirstChild()); case Token.VOID: case Token.NEG: return isImmutableValue(n.getFirstChild()); case Token.NAME: String name = n.getString(); // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return "undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name); } return false; } /** * Returns true if the operator on this node is symmetric */ static boolean isSymmetricOperation(Node n) { switch (n.getType()) { case Token.EQ: // equal case Token.NE: // not equal case Token.SHEQ: // exactly equal case Token.SHNE: // exactly not equal case Token.MUL: // multiply, unlike add it only works on numbers // or results NaN if any of the operators is not a number return true; } return false; } /** * Returns true if the operator on this node is relational. * the returned set does not include the equalities. */ static boolean isRelationalOperation(Node n) { switch (n.getType()) { case Token.GT: // equal case Token.GE: // not equal case Token.LT: // exactly equal case Token.LE: // exactly not equal return true; } return false; } /** * Returns the inverse of an operator if it is invertible. * ex. '>' ==> '<' */ static int getInverseOperator(int type) { switch (type) { case Token.GT: return Token.LT; case Token.LT: return Token.GT; case Token.GE: return Token.LE; case Token.LE: return Token.GE; } return Token.ERROR; } /** * Returns true if this is a literal value. We define a literal value * as any node that evaluates to the same thing regardless of when or * where it is evaluated. So /xyz/ and [3, 5] are literals, but * the name a is not. * * Function literals do not meet this definition, because they * lexically capture variables. For example, if you have * <code> * function() { return a; } * </code

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>> * If it is evaluated in a different scope, then it * captures a different variable. Even if the function did not read * any captured variables directly, it would still fail this definition, * because it affects the lifecycle of variables in the enclosing scope. * * However, a function literal with respect to a particular scope is * a literal. * * @param includeFunctions If true, all function expressions will be * treated as literals. */ static boolean isLiteralValue(Node n, boolean includeFunctions) { switch (n.getType()) { case Token.CAST: return isLiteralValue(n.getFirstChild(), includeFunctions); case Token.ARRAYLIT: for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if ((!child.isEmpty()) && !isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.REGEXP: // Return true only if all children are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.OBJECTLIT: // Return true only if all values are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child.getFirstChild(), includeFunctions)) { return false; } } return true; case Token.FUNCTION: return includeFunctions && !NodeUtil.isFunctionDeclaration(n); default: return isImmutableValue(n); } } /** * Determines whether the given value may be assigned to a define. * * @param val The value being assigned. * @param defines The list of names of existing defines. */ static boolean isValidDefineValue(Node val, Set<String> defines) { switch (val.getType()) { case Token.STRING: case Token.NUMBER: case Token.TRUE: case Token.FALSE: return true; // Binary operators are only valid if both children are valid. case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return isValidDefineValue(val.getFirstChild(), defines) && isValidDefineValue(val.getLastChild(), defines); // Unary operators are valid if the child is valid. case Token.NOT: case Token.NEG: case Token.POS: return isValidDefineValue(val.getFirstChild(), defines); // Names are valid if and only if they are defines themselves. case Token.NAME: case Token.GETPROP: if (val.isQualifiedName()) { return defines

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.contains(val.getQualifiedName()); } } return false; } /** * Returns whether this a BLOCK node with no children. * * @param block The node. */ static boolean isEmptyBlock(Node block) { if (!block.isBlock()) { return false; } for (Node n = block.getFirstChild(); n != null; n = n.getNext()) { if (!n.isEmpty()) { return false; } } return true; } static boolean isSimpleOperator(Node n) { return isSimpleOperatorType(n.getType()); } /** * A "simple" operator is one whose children are expressions, * has no direct side-effects (unlike '+='), and has no * conditional aspects (unlike '||'). */ static boolean isSimpleOperatorType(int type) { switch (type) { case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.COMMA: case Token.DIV: case Token.EQ: case Token.GE: case Token.GETELEM: case Token.GETPROP: case Token.GT: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.NOT: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.TYPEOF: case Token.VOID: case Token.POS: case Token.NEG: case Token.URSH: return true; default: return false; } } /** * Creates an EXPR_RESULT. * * @param child The expression itself. * @return Newly created EXPR node with the child as subexpression. */ static Node newExpr(Node child) { return IR.exprResult(child).srcref(child); } /** * Returns true if the node may create new mutable state, or change existing * state. * * @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a> */ static boolean mayEffectMutableState(Node n) { return mayEffectMutableState(n, null); } static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, true, compiler); } /** * Returns true if the node which may have side effects when executed. */ static boolean mayHaveSideEffects(Node n) { return mayHaveSideEffects(n, null); } static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, false, compiler); } /** * Returns true if some node in n's subtree changes application state. * If {@code checkForNewObjects} is true, we assume that newly created * mutable objects (like object literals) change state. Otherwise, we assume * that they have no side effects. */ private static boolean checkForStateChange

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Helper( Node n, boolean checkForNewObjects, AbstractCompiler compiler) { // Rather than id which ops may have side effects, id the ones // that we know to be safe switch (n.getType()) { // other side-effect free statements and expressions case Token.CAST: case Token.AND: case Token.BLOCK: case Token.EXPR_RESULT: case Token.HOOK: case Token.IF: case Token.IN: case Token.PARAM_LIST: case Token.NUMBER: case Token.OR: case Token.THIS: case Token.TRUE: case Token.FALSE: case Token.NULL: case Token.STRING: case Token.STRING_KEY: case Token.SWITCH: case Token.TRY: case Token.EMPTY: break; // Throws are by definition side effects case Token.THROW: return true; case Token.OBJECTLIT: if (checkForNewObjects) { return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (checkForStateChangeHelper( c.getFirstChild(), checkForNewObjects, compiler)) { return true; } } return false; case Token.ARRAYLIT: case Token.REGEXP: if (checkForNewObjects) { return true; } break; case Token.VAR: // empty var statement (no declaration) case Token.NAME: // variable by itself if (n.getFirstChild() != null) { return true; } break; case Token.FUNCTION: // Function expressions don't have side-effects, but function // declarations change the namespace. Either way, we don't need to // check the children, since they aren't executed at declaration time. return checkForNewObjects || !isFunctionExpression(n); case Token.NEW: if (checkForNewObjects) { return true; } if (!constructorCallHasSideEffects(n)) { // loop below will see if the constructor parameters have // side-effects break; } return true; case Token.CALL: // calls to functions that have no side effects have the no // side effect property set. if (!functionCallHasSideEffects(n, compiler)) { // loop below will see if the function parameters have // side-effects break; } return true; default: if (isSimpleOperator(n)) { break; } if (isAssignmentOp(n)) { Node assignTarget = n.getFirstChild(); if (assignTarget.isName()) { return true; } // Assignments will have side effects if // a) The RHS has side effects, or // b) The LHS has side effects, or // c) A name on the LHS will exist beyond the life of this statement. if (checkForStateChangeHelper( n.getFirstChild(), checkForNewObjects, compiler) || checkForStateChangeHelper( n.getLastChild(), checkForNewObjects, compiler)) { return true; } if (isGet(assignTarget)) { // If the object being assigned to is a local object, don't // consider

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> this a side-effect as it can't be referenced // elsewhere. Don't do this recursively as the property might // be an alias of another object, unlike a literal below. Node current = assignTarget.getFirstChild(); if (evaluatesToLocalValue(current)) { return false; } // A literal value as defined by "isLiteralValue" is guaranteed // not to be an alias, or any components which are aliases of // other objects. // If the root object is a literal don't consider this a // side-effect. while (isGet(current)) { current = current.getFirstChild(); } return !isLiteralValue(current, true); } else { // TODO(johnlenz): remove this code and make this an exception. This // is here only for legacy reasons, the AST is not valid but // preserve existing behavior. return !isLiteralValue(assignTarget, true); } } return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) { return true; } } return false; } /** * Do calls to this constructor have side effects? * * @param callNode - constructor call node */ static boolean constructorCallHasSideEffects(Node callNode) { return constructorCallHasSideEffects(callNode, null); } static boolean constructorCallHasSideEffects( Node callNode, AbstractCompiler compiler) { if (!callNode.isNew()) { throw new IllegalStateException( "Expected NEW node, got " + Token.name(callNode.getType())); } if (callNode.isNoSideEffectsCall()) { return false; } if (callNode.isOnlyModifiesArgumentsCall() && allArgsUnescapedLocal(callNode)) { return false; } Node nameNode = callNode.getFirstChild(); if (nameNode.isName() && CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) { return false; } return true; } // A list of built-in object creation or primitive type cast functions that // can also be called as constructors but lack side-effects. // TODO(johnlenz): consider adding an extern annotation for this. private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS = ImmutableSet.of( "Object", "Array", "String", "Number", "Boolean", "RegExp", "Error"); private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS = ImmutableSet.of("toString", "valueOf"); private static final Set<String> REGEXP_METHODS = ImmutableSet.of("test", "exec"); private static final Set<String> STRING_REGEXP_METHODS = ImmutableSet.of("match", "replace", "search", "split"); /** * Returns true if calls to this function have side effects. * * @param callNode - function call node */ static boolean functionCallHasSideEffects(Node callNode) { return functionCallHasSideEffects(callNode

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>, null); } /** * Returns true if calls to this function have side effects. * * @param callNode The call node to inspected. * @param compiler A compiler object to provide program state changing * context information. Can be null. */ static boolean functionCallHasSideEffects( Node callNode, @Nullable AbstractCompiler compiler) { if (!callNode.isCall()) { throw new IllegalStateException( "Expected CALL node, got " + Token.name(callNode.getType())); } if (callNode.isNoSideEffectsCall()) { return false; } if (callNode.isOnlyModifiesArgumentsCall() && allArgsUnescapedLocal(callNode)) { return false; } Node nameNode = callNode.getFirstChild(); // Built-in functions with no side effects. if (nameNode.isName()) { String name = nameNode.getString(); if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) { return false; } } else if (nameNode.isGetProp()) { if (callNode.hasOneChild() && OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains( nameNode.getLastChild().getString())) { return false; } if (callNode.isOnlyModifiesThisCall() && evaluatesToLocalValue(nameNode.getFirstChild())) { return false; } // Math.floor has no side-effects. // TODO(nicksantos): This is a terrible terrible hack, until // I create a definitionProvider that understands namespacing. if (nameNode.getFirstChild().isName()) { if ("Math.floor".equals(nameNode.getQualifiedName())) { return false; } } if (compiler != null && !compiler.hasRegExpGlobalReferences()) { if (nameNode.getFirstChild().isRegExp() && REGEXP_METHODS.contains(nameNode.getLastChild().getString())) { return false; } else if (nameNode.getFirstChild().isString() && STRING_REGEXP_METHODS.contains( nameNode.getLastChild().getString())) { Node param = nameNode.getNext(); if (param != null && (param.isString() || param.isRegExp())) { return false; } } } } return true; } /** * @return Whether the call has a local result. */ static boolean callHasLocalResult(Node n) { Preconditions.checkState(n.isCall()); return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0; } /** * @return Whether the new has a local result. */ static boolean newHasLocalResult(Node n) { Preconditions.checkState(n.isNew()); return n.isOnlyModifiesThisCall(); } /** * Returns true if the current node's type implies side effects. * * This is a non-recursive version of the may have side effects * check; used to check wherever the current node's type is one of * the reason's why a subtree has side effects. */ static boolean nodeTypeMayHaveSideEffects

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(Node n) { return nodeTypeMayHaveSideEffects(n, null); } static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) { if (isAssignmentOp(n)) { return true; } switch(n.getType()) { case Token.DELPROP: case Token.DEC: case Token.INC: case Token.THROW: return true; case Token.CALL: return NodeUtil.functionCallHasSideEffects(n, compiler); case Token.NEW: return NodeUtil.constructorCallHasSideEffects(n, compiler); case Token.NAME: // A variable definition. return n.hasChildren(); default: return false; } } static boolean allArgsUnescapedLocal(Node callOrNew) { for (Node arg = callOrNew.getFirstChild().getNext(); arg != null; arg = arg.getNext()) { if (!evaluatesToLocalValue(arg)) { return false; } } return true; } /** * @return Whether the tree can be affected by side-effects or * has side-effects. */ static boolean canBeSideEffected(Node n) { Set<String> emptySet = Collections.emptySet(); return canBeSideEffected(n, emptySet); } /** * @param knownConstants A set of names known to be constant value at * node 'n' (such as locals that are last written before n can execute). * @return Whether the tree can be affected by side-effects or * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; case Token.FUNCTION: // Function expression are not changed by side-effects, // and function declarations are not part of expressions. Preconditions.checkState(isFunctionExpression(n)); return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: * 3 logical-or || * 4 logical-and && * 5 bitwise-or | * 6 bitwise-xor ^ * 7 bitwise-and & * 8 equality == != * 9 relational < <= > >= * 10 bitwise shift << >> >>> * 11 addition/subtraction + - *

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> 12 multiply/divide * / % * 13 negation/increment ! ~ - ++ -- * 14 call, member () [] . */ static int precedence(int type) { int precedence = precedenceWithDefault(type); if (precedence != -1) { return precedence; } throw new Error("Unknown precedence for " + Token.name(type) + " (type " + type + ")"); } static int precedenceWithDefault(int type) { switch (type) { case Token.COMMA: return 0; case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN: return 1; case Token.HOOK: return 2; // ?: operator case Token.OR: return 3; case Token.AND: return 4; case Token.BITOR: return 5; case Token.BITXOR: return 6; case Token.BITAND: return 7; case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: return 8; case Token.LT: case Token.GT: case Token.LE: case Token.GE: case Token.INSTANCEOF: case Token.IN: return 9; case Token.LSH: case Token.RSH: case Token.URSH: return 10; case Token.SUB: case Token.ADD: return 11; case Token.MUL: case Token.MOD: case Token.DIV: return 12; case Token.INC: case Token.DEC: case Token.NEW: case Token.DELPROP: case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: return 13; case Token.CALL: case Token.GETELEM: case Token.GETPROP: // Data values case Token.ARRAYLIT: case Token.EMPTY: // TODO(johnlenz): remove this. case Token.FALSE: case Token.FUNCTION: case Token.NAME: case Token.NULL: case Token.NUMBER: case Token.OBJECTLIT: case Token.REGEXP: case Token.STRING: case Token.STRING_KEY: case Token.THIS: case Token.TRUE: return 15; case Token.CAST: return 16; default: // Statements are lower precedence than expressions. return -1; } } static boolean isUndefined(Node n) { switch (n.getType()) { case Token.VOID: return true; case Token.NAME: return n.getString().equals("undefined"); } return false; } static

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> boolean isNullOrUndefined(Node n) { return n.isNull() || isUndefined(n); } static final Predicate<Node> IMMUTABLE_PREDICATE = new Predicate<Node>() { @Override public boolean apply(Node n) { return isImmutableValue(n); } }; static boolean isImmutableResult(Node n) { return allResultsMatch(n, IMMUTABLE_PREDICATE); } /** * Apply the supplied predicate against * all possible result Nodes of the expression. */ static boolean allResultsMatch(Node n, Predicate<Node> p) { switch (n.getType()) { case Token.CAST: return allResultsMatch(n.getFirstChild(), p); case Token.ASSIGN: case Token.COMMA: return allResultsMatch(n.getLastChild(), p); case Token.AND: case Token.OR: return allResultsMatch(n.getFirstChild(), p) && allResultsMatch(n.getLastChild(), p); case Token.HOOK: return allResultsMatch(n.getFirstChild().getNext(), p) && allResultsMatch(n.getLastChild(), p); default: return p.apply(n); } } /** * Apply the supplied predicate against * all possible result Nodes of the expression. */ static boolean anyResultsMatch(Node n, Predicate<Node> p) { switch (n.getType()) { case Token.CAST: return anyResultsMatch(n.getFirstChild(), p); case Token.ASSIGN: case Token.COMMA: return anyResultsMatch(n.getLastChild(), p); case Token.AND: case Token.OR: return anyResultsMatch(n.getFirstChild(), p) || anyResultsMatch(n.getLastChild(), p); case Token.HOOK: return anyResultsMatch(n.getFirstChild().getNext(), p) || anyResultsMatch(n.getLastChild(), p); default: return p.apply(n); } } static class NumbericResultPredicate implements Predicate<Node> { @Override public boolean apply(Node n) { return isNumericResultHelper(n); } } static final NumbericResultPredicate NUMBERIC_RESULT_PREDICATE = new NumbericResultPredicate(); /** * Returns true if the result of node evaluation is always a number */ static boolean isNumericResult(Node n) { return allResultsMatch(n, NUMBERIC_RESULT_PREDICATE); } static boolean isNumericResultHelper(Node n) { switch (n.getType()) { case Token.ADD: return !mayBeString(n.getFirstChild()) && !mayBeString(n.getLastChild()); case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.BITAND: case Token.LSH: case Token.RSH: case Token.URSH: case Token.SUB: case Token.MUL: case Token.MOD: case Token.DIV: case Token.INC: case Token.DEC: case Token.POS: case Token.NEG: case Token.NUMBER: return true; case Token

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.NAME: String name = n.getString(); if (name.equals("NaN")) { return true; } if (name.equals("Infinity")) { return true; } return false; default: return false; } } static class BooleanResultPredicate implements Predicate<Node> { @Override public boolean apply(Node n) { return isBooleanResultHelper(n); } } static final BooleanResultPredicate BOOLEAN_RESULT_PREDICATE = new BooleanResultPredicate(); /** * @return Whether the result of node evaluation is always a boolean */ static boolean isBooleanResult(Node n) { return allResultsMatch(n, BOOLEAN_RESULT_PREDICATE); } static boolean isBooleanResultHelper(Node n) { switch (n.getType()) { // Primitives case Token.TRUE: case Token.FALSE: // Comparisons case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.LT: case Token.GT: case Token.LE: case Token.GE: // Queries case Token.IN: case Token.INSTANCEOF: // Inversion case Token.NOT: // delete operator returns a boolean. case Token.DELPROP: return true; default: return false; } } static class MayBeStringResultPredicate implements Predicate<Node> { @Override public boolean apply(Node n) { return mayBeStringHelper(n); } } static final MayBeStringResultPredicate MAY_BE_STRING_PREDICATE = new MayBeStringResultPredicate(); /** * @returns Whether the results is possibly a string. */ static boolean mayBeString(Node n) { return mayBeString(n, true); } static boolean mayBeString(Node n, boolean recurse) { if (recurse) { return anyResultsMatch(n, MAY_BE_STRING_PREDICATE); } else { return mayBeStringHelper(n); } } static boolean mayBeStringHelper(Node n) { return !isNumericResult(n) && !isBooleanResult(n) && !isUndefined(n) && !n.isNull(); } /** * Returns true if the operator is associative. * e.g. (a * b) * c = a * (b * c) * Note: "+" is not associative because it is also the concatenation * for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2 */ static boolean isAssociative(int type) { switch (type) { case Token.MUL: case Token.AND: case Token.OR: case Token.BITOR: case Token.BITXOR: case Token.BITAND: return true; default: return false; } } /** * Returns true if the operator is commutative. * e.g. (a * b) * c = c * (b * a) * Note 1: "+" is not commutative because it is also the concatenation

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> * for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2 * Note 2: only operations on literals and pure functions are commutative. */ static boolean isCommutative(int type) { switch (type) { case Token.MUL: case Token.BITOR: case Token.BITXOR: case Token.BITAND: return true; default: return false; } } static boolean isAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: return true; } return false; } static int getOpFromAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN_BITOR: return Token.BITOR; case Token.ASSIGN_BITXOR: return Token.BITXOR; case Token.ASSIGN_BITAND: return Token.BITAND; case Token.ASSIGN_LSH: return Token.LSH; case Token.ASSIGN_RSH: return Token.RSH; case Token.ASSIGN_URSH: return Token.URSH; case Token.ASSIGN_ADD: return Token.ADD; case Token.ASSIGN_SUB: return Token.SUB; case Token.ASSIGN_MUL: return Token.MUL; case Token.ASSIGN_DIV: return Token.DIV; case Token.ASSIGN_MOD: return Token.MOD; } throw new IllegalArgumentException("Not an assignment op:" + n); } /** * Determines if the given node contains a function statement or function * expression. */ static boolean containsFunction(Node n) { return containsType(n, Token.FUNCTION); } /** * Returns true if the shallow scope contains references to 'this' keyword */ static boolean referencesThis(Node n) { Node start = (n.isFunction()) ? n.getLastChild() : n; return containsType(start, Token.THIS, MATCH_NOT_FUNCTION); } /** * Is this a GETPROP or GETELEM node? */ static boolean isGet(Node n) { return n.isGetProp() || n.isGetElem(); } /** * Is this node the name of a variable being declared? * * @param n The node * @return True if {@code n} is NAME and {@code parent} is VAR */ static boolean isVarDeclaration(Node n) { // There is no need to verify that parent != null because a NAME node // always has a parent in a valid parse tree. return n.isName() && n.getParent().isVar(); } /** * For an assignment or variable declaration get the assigned value. * @return The value node representing the new value.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> */ static Node getAssignedValue(Node n) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); if (parent.isVar()) { return n.getFirstChild(); } else if (parent.isAssign() && parent.getFirstChild() == n) { return n.getNext(); } else { return null; } } /** * Is this node an assignment expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is ASSIGN */ static boolean isExprAssign(Node n) { return n.isExprResult() && n.getFirstChild().isAssign(); } /** * Is this node a call expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is CALL */ static boolean isExprCall(Node n) { return n.isExprResult() && n.getFirstChild().isCall(); } /** * @return Whether the node represents a FOR-IN loop. */ static boolean isForIn(Node n) { return n.isFor() && n.getChildCount() == 3; } /** * Determines whether the given node is a FOR, DO, or WHILE node. */ static boolean isLoopStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * @param n The node to inspect. * @return If the node, is a FOR, WHILE, or DO, it returns the node for * the code BLOCK, null otherwise. */ static Node getLoopCodeBlock(Node n) { switch (n.getType()) { case Token.FOR: case Token.WHILE: return n.getLastChild(); case Token.DO: return n.getFirstChild(); default: return null; } } /** * @return Whether the specified node has a loop parent that * is within the current scope. */ static boolean isWithinLoop(Node n) { for (Node parent : n.getAncestors()) { if (NodeUtil.isLoopStructure(parent)) { return true; } if (parent.isFunction()) { break; } } return false; } /** * Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node. */ static boolean isControlStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.WITH: case Token.IF: case Token.LABEL: case Token.TRY: case Token.CATCH: case Token.SWITCH: case Token.CASE: case Token.DEFAULT_CASE: return true; default: return false; } } /** * Determines whether the given node is code node for FOR, DO, * WH

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>ILE, WITH, or IF node. */ static boolean isControlStructureCodeBlock(Node parent, Node n) { switch (parent.getType()) { case Token.FOR: case Token.WHILE: case Token.LABEL: case Token.WITH: return parent.getLastChild() == n; case Token.DO: return parent.getFirstChild() == n; case Token.IF: return parent.getFirstChild() != n; case Token.TRY: return parent.getFirstChild() == n || parent.getLastChild() == n; case Token.CATCH: return parent.getLastChild() == n; case Token.SWITCH: case Token.CASE: return parent.getFirstChild() != n; case Token.DEFAULT_CASE: return true; default: Preconditions.checkState(isControlStructure(parent)); return false; } } /** * Gets the condition of an ON_TRUE / ON_FALSE CFG edge. * @param n a node with an outgoing conditional CFG edge * @return the condition node or null if the condition is not obviously a node */ static Node getConditionExpression(Node n) { switch (n.getType()) { case Token.IF: case Token.WHILE: return n.getFirstChild(); case Token.DO: return n.getLastChild(); case Token.FOR: switch (n.getChildCount()) { case 3: return null; case 4: return n.getFirstChild().getNext(); } throw new IllegalArgumentException("malformed 'for' statement " + n); case Token.CASE: return null; } throw new IllegalArgumentException(n + " does not have a condition."); } /** * @return Whether the node is of a type that contain other statements. */ static boolean isStatementBlock(Node n) { return n.isScript() || n.isBlock(); } /** * @return Whether the node is used as a statement. */ static boolean isStatement(Node n) { return isStatementParent(n.getParent()); } static boolean isStatementParent(Node parent) { // It is not possible to determine definitely if a node is a statement // or not if it is not part of the AST. A FUNCTION node can be // either part of an expression or a statement. Preconditions.checkState(parent != null); switch (parent.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.LABEL: return true; default: return false; } } /** Whether the node is part of a switch statement. */ static boolean isSwitchCase(Node n) { return n.isCase() || n.isDefaultCase(); } /** * @return Whether the name is a reference to a variable, function or * function parameter (not a label or a empty function expression name). */ static boolean isReferenceName(Node n) { return n.isName() && !n.getString().isEmpty(); } /** Whether the child node is the FINALLY block of a try. */ static boolean isTryFinallyNode(Node parent, Node child) { return parent.isTry() && parent.getChildCount

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> return false; } return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) || (NodeUtil.isForIn(parent) && parent.getFirstChild() == n) || parent.isVar() || (parent.isFunction() && parent.getFirstChild() == n) || parent.isDec() || parent.isInc() || parent.isParamList() || parent.isCatch(); } /** * Determines whether a node represents an object literal key * (e.g. key1 in {key1: value1, key2: value2}). * * @param node A node */ static boolean isObjectLitKey(Node node) { switch (node.getType()) { case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: return true; } return false; } /** * Get the name of an object literal key. * * @param key A node */ static String getObjectLitKeyName(Node key) { switch (key.getType()) { case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: return key.getString(); } throw new IllegalStateException("Unexpected node type: " + key); } /** * @param key A OBJECTLIT key node. * @return The type expected when using the key. */ static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) { if (valueType != null) { switch (key.getType()) { case Token.GETTER_DEF: // GET must always return a function type. if (valueType.isFunctionType()) { FunctionType fntype = valueType.toMaybeFunctionType(); valueType = fntype.getReturnType(); } else { return null; } break; case Token.SETTER_DEF: if (valueType.isFunctionType()) { // SET must always return a function type. FunctionType fntype = valueType.toMaybeFunctionType(); Node param = fntype.getParametersNode().getFirstChild(); // SET function must always have one parameter. valueType = param.getJSType(); } else { return null; } break; } } return valueType; } /** * Determines whether a node represents an object literal get or set key * (e.g. key1 in {get key1() {}, set key2(a){}). * * @param node A node */ static boolean isGetOrSetKey(Node node) { switch (node.getType()) { case Token.GETTER_DEF: case Token.SETTER_DEF: return true; } return false; } /** * Converts an operator's token value (see {@link Token}) to a string * representation. * * @param operator the operator's token value to convert * @return the string representation or {@code null} if the token value is * not an operator */ static String opToStr(int operator) { switch (operator) { case Token.BITOR: return "|"; case Token.OR: return

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> redeclareVarsInsideBranch(Node branch) { Collection<Node> vars = getVarsDeclaredInBranch(branch); if (vars.isEmpty()) { return; } Node parent = getAddingRoot(branch); for (Node nameNode : vars) { Node var = IR.var( IR.name(nameNode.getString()) .srcref(nameNode)) .srcref(nameNode); copyNameAnnotations(nameNode, var.getFirstChild()); parent.addChildToFront(var); } } /** * Copy any annotations that follow a named value. * @param source * @param destination */ static void copyNameAnnotations(Node source, Node destination) { if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) { destination.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } /** * Gets a Node at the top of the current scope where we can add new var * declarations as children. */ private static Node getAddingRoot(Node n) { Node addingRoot = null; Node ancestor = n; while (null != (ancestor = ancestor.getParent())) { int type = ancestor.getType(); if (type == Token.SCRIPT) { addingRoot = ancestor; break; } else if (type == Token.FUNCTION) { addingRoot = ancestor.getLastChild(); break; } } // make sure that the adding root looks ok Preconditions.checkState(addingRoot.isBlock() || addingRoot.isScript()); Preconditions.checkState(addingRoot.getFirstChild() == null || !addingRoot.getFirstChild().isScript()); return addingRoot; } /** * Creates a node representing a qualified name. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @return A NAME or GETPROP node */ public static Node newQualifiedNameNode( CodingConvention convention, String name) { int endPos = name.indexOf('.'); if (endPos == -1) { return newName(convention, name); } Node node; String nodeName = name.substring(0, endPos); if ("this".equals(nodeName)) { node = IR.thisNode(); } else { node = newName(convention, nodeName); } int startPos; do { startPos = endPos + 1; endPos = name.indexOf('.', startPos); String part = (endPos == -1 ? name.substring(startPos) : name.substring(startPos, endPos)); Node propNode = IR.string(part); if (convention.isConstantKey(part)) { propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } node = IR.getprop(node, propNode); } while (endPos != -1); return node; } /** * Creates a node representing a qualified name. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @return A NAME or GETPROP node */ public static Node newQualifiedNameNodeDeclaration( CodingConvention convention, String name

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.isVar()) { String name = n.getString(); if (!vars.containsKey(name)) { vars.put(name, n); } } } } } /** * Retrieves vars declared in the current node tree, excluding descent scopes. */ static Collection<Node> getVarsDeclaredInBranch(Node root) { VarCollector collector = new VarCollector(); visitPreOrder( root, collector, MATCH_NOT_FUNCTION); return collector.vars.values(); } /** * @return {@code true} if the node an assignment to a prototype property of * some constructor. */ static boolean isPrototypePropertyDeclaration(Node n) { return isExprAssign(n) && isPrototypeProperty(n.getFirstChild().getFirstChild()); } /** * @return Whether the node represents a qualified prototype property. */ static boolean isPrototypeProperty(Node n) { String lhsString = n.getQualifiedName(); return lhsString != null && lhsString.contains(".prototype."); } /** * @return The class name part of a qualified prototype name. */ static Node getPrototypeClassName(Node qName) { Node cur = qName; while (cur.isGetProp()) { if (cur.getLastChild().getString().equals("prototype")) { return cur.getFirstChild(); } else { cur = cur.getFirstChild(); } } return null; } /** * @return The string property name part of a qualified prototype name. */ static String getPrototypePropertyName(Node qName) { String qNameStr = qName.getQualifiedName(); int prototypeIdx = qNameStr.lastIndexOf(".prototype."); int memberIndex = prototypeIdx + ".prototype".length() + 1; return qNameStr.substring(memberIndex); } /** * Create a node for an empty result expression: * "void 0" */ static Node newUndefinedNode(Node srcReferenceNode) { Node node = IR.voidNode(IR.number(0)); if (srcReferenceNode != null) { node.copyInformationFromForTree(srcReferenceNode); } return node; } /** * Create a VAR node containing the given name and initial value expression. */ static Node newVarNode(String name, Node value) { Node nodeName = IR.name(name); if (value != null) { Preconditions.checkState(value.getNext() == null); nodeName.addChildToBack(value); nodeName.srcref(value); } Node var = IR.var(nodeName).srcref(nodeName); return var; } /** * A predicate for matching name nodes with the specified node. */ private static class MatchNameNode implements Predicate<Node>{ final String name; MatchNameNode(String name){ this.name = name; } @Override public boolean apply(Node n) { return n.isName() && n.getString().equals(name); } } /** * A predicate for matching nodes with the specified type. */ static class MatchNodeType implements Predicate<Node>{ final int type; MatchNodeType(int type){ this.type = type

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>; } @Override public boolean apply(Node n) { return n.getType() == type; } } /** * A predicate for matching var or function declarations. */ static class MatchDeclaration implements Predicate<Node> { @Override public boolean apply(Node n) { return isFunctionDeclaration(n) || n.isVar(); } } /** * A predicate for matching anything except function nodes. */ private static class MatchNotFunction implements Predicate<Node>{ @Override public boolean apply(Node n) { return !n.isFunction(); } } static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction(); /** * A predicate for matching statements without exiting the current scope. */ static class MatchShallowStatement implements Predicate<Node>{ @Override public boolean apply(Node n) { Node parent = n.getParent(); return n.isBlock() || (!n.isFunction() && (parent == null || isControlStructure(parent) || isStatementBlock(parent))); } } /** * Finds the number of times a type is referenced within the node tree. */ static int getNodeTypeReferenceCount( Node node, int type, Predicate<Node> traverseChildrenPred) { return getCount(node, new MatchNodeType(type), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNameNode(name), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name) { return isNameReferenced(node, name, Predicates.<Node>alwaysTrue()); } /** * Finds the number of times a simple name is referenced within the node tree. */ static int getNameReferenceCount(Node node, String name) { return getCount( node, new MatchNameNode(name), Predicates.<Node>alwaysTrue()); } /** * @return Whether the predicate is true for the node or any of its children. */ static boolean has(Node node, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { if (pred.apply(node)) { return true; } if (!traverseChildrenPred.apply(node)) { return false; } for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { if (has(c, pred, traverseChildrenPred)) { return true; } } return false; } /** * @return The number of times the the predicate is true for the node * or any of its children. */ static int getCount( Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { int total = 0; if (pred.apply(n)) { total++; } if (traverseChildrenPred.apply(n)) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { total += getCount(c, pred

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { return convention.isConstantKey(node.getString()); } else if (node.isName()) { return convention.isConstant(node.getString()); } return false; } /** * Get the JSDocInfo for a function. */ public static JSDocInfo getFunctionJSDocInfo(Node n) { Preconditions.checkState(n.isFunction()); JSDocInfo fnInfo = n.getJSDocInfo(); if (fnInfo == null && NodeUtil.isFunctionExpression(n)) { // Look for the info on other nodes. Node parent = n.getParent(); if (parent.isAssign()) { // on ASSIGNs fnInfo = parent.getJSDocInfo(); } else if (parent.isName()) { // on var NAME = function() { ... }; fnInfo = parent.getParent().getJSDocInfo(); } } return fnInfo; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ public static String getSourceName(Node n) { String sourceName = null; while (sourceName == null && n != null) { sourceName = n.getSourceFileName(); n = n.getParent(); } return sourceName; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ public static StaticSourceFile getSourceFile(Node n) { StaticSourceFile sourceName = null; while (sourceName == null && n != null) { sourceName = n.getStaticSourceFile(); n = n.getParent(); } return sourceName; } /** * @param n The node. * @return The InputId property on the node or its ancestors. */ public static InputId getInputId(Node n) { while (n != null && !n.isScript()) { n = n.getParent(); } return (n != null && n.isScript()) ? n.getInputId() : null; } /** * A new CALL node with the "FREE_CALL" set based on call target. */ static Node newCallNode(Node callTarget, Node... parameters) { boolean isFreeCall = !isGet(callTarget); Node call = IR.call(callTarget); call.putBooleanProp(Node.FREE_CALL, isFreeCall); for (Node parameter : parameters) { call.addChildToBack(parameter); } return call; } /** * @return Whether the node is known to be a value that is not referenced * elsewhere. */ static boolean evaluatesToLocalValue(Node value) { return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse()); } /** * @param locals A predicate to apply to unknown local values. * @return Whether the node is known to be a value that is not a reference * outside the expression scope. */ static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) { switch (value.getType()) { case Token.CAST: return evaluatesToLocalValue(value.getFirstChild(), locals); case Token.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> if no such argument exists. */ static Node getArgumentForCallOrNew(Node call, int index) { Preconditions.checkState(isCallOrNew(call)); return getNthSibling( call.getFirstChild().getNext(), index); } /** * Returns whether this is a target of a call or new. */ static boolean isCallOrNewTarget(Node target) { Node parent = target.getParent(); return parent != null && NodeUtil.isCallOrNew(parent) && parent.getFirstChild() == target; } private static boolean isToStringMethodCall(Node call) { Node getNode = call.getFirstChild(); if (isGet(getNode)) { Node propNode = getNode.getLastChild(); return propNode.isString() && "toString".equals(propNode.getString()); } return false; } /** Find the best JSDoc for the given node. */ static JSDocInfo getBestJSDocInfo(Node n) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { Node parent = n.getParent(); if (parent == null) { return null; } if (parent.isName()) { return getBestJSDocInfo(parent); } else if (parent.isAssign()) { return getBestJSDocInfo(parent); } else if (isObjectLitKey(parent)) { return parent.getJSDocInfo(); } else if (parent.isFunction()) { return parent.getJSDocInfo(); } else if (parent.isVar() && parent.hasOneChild()) { return parent.getJSDocInfo(); } else if ((parent.isHook() && parent.getFirstChild() != n) || parent.isOr() || parent.isAnd() || (parent.isComma() && parent.getFirstChild() != n)) { return getBestJSDocInfo(parent); } else if (parent.isCast()) { return parent.getJSDocInfo(); } } return info; } /** Find the l-value that the given r-value is being assigned to. */ static Node getBestLValue(Node n) { Node parent = n.getParent(); boolean isFunctionDeclaration = isFunctionDeclaration(n); if (isFunctionDeclaration) { return n.getFirstChild(); } else if (parent.isName()) { return parent; } else if (parent.isAssign()) { return parent.getFirstChild(); } else if (isObjectLitKey(parent)) { return parent; } else if ( (parent.isHook() && parent.getFirstChild() != n) || parent.isOr() || parent.isAnd() || (parent.isComma() && parent.getFirstChild() != n)) { return getBestLValue(parent); } else if (parent.isCast()) { return getBestLValue(parent); } return null; } /** Gets the r-value of a node returned by getBestLValue. */ static Node getRValueOfLValue(Node n) { Node parent = n.getParent(); switch (parent.getType()) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> case Token.ASSIGN: return n.getNext(); case Token.VAR: return n.getFirstChild(); case Token.FUNCTION: return parent; } return null; } /** Get the owner of the given l-value node. */ static Node getBestLValueOwner(@Nullable Node lValue) { if (lValue == null || lValue.getParent() == null) { return null; } if (isObjectLitKey(lValue)) { return getBestLValue(lValue.getParent()); } else if (isGet(lValue)) { return lValue.getFirstChild(); } return null; } /** Get the name of the given l-value node. */ static String getBestLValueName(@Nullable Node lValue) { if (lValue == null || lValue.getParent() == null) { return null; } if (isObjectLitKey(lValue)) { Node owner = getBestLValue(lValue.getParent()); if (owner != null) { String ownerName = getBestLValueName(owner); if (ownerName != null) { return ownerName + "." + getObjectLitKeyName(lValue); } } return null; } return lValue.getQualifiedName(); } /** * @returns false iff the result of the expression is not consumed. */ static boolean isExpressionResultUsed(Node expr) { // TODO(johnlenz): consider sharing some code with trySimpleUnusedResult. Node parent = expr.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.EXPR_RESULT: return false; case Token.CAST: return isExpressionResultUsed(parent); case Token.HOOK: case Token.AND: case Token.OR: return (expr == parent.getFirstChild()) ? true : isExpressionResultUsed(parent); case Token.COMMA: Node gramps = parent.getParent(); if (gramps.isCall() && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first // expression to a comma to be a no-op if it's used to indirect // an eval. This we pretend that this is "used". if (expr == parent.getFirstChild() && parent.getChildCount() == 2 && expr.getNext().isName() && "eval".equals(expr.getNext().getString())) { return true; } } return (expr == parent.getFirstChild()) ? false : isExpressionResultUsed(parent); case Token.FOR: if (!NodeUtil.isForIn(parent)) { // Only an expression whose result is in the condition part of the // expression is used. return (parent.getChildAtIndex(1) == expr); } break; } return true; } /** * @param n The expression to check. * @return Whether the expression is unconditionally executed only once in the * containing execution scope. */ static boolean isExecutedExactlyOnce(Node

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> n) { inspect: do { Node parent = n.getParent(); switch (parent.getType()) { case Token.IF: case Token.HOOK: case Token.AND: case Token.OR: if (parent.getFirstChild() != n) { return false; } // other ancestors may be conditional continue inspect; case Token.FOR: if (NodeUtil.isForIn(parent)) { if (parent.getChildAtIndex(1) != n) { return false; } } else { if (parent.getFirstChild() != n) { return false; } } // other ancestors may be conditional continue inspect; case Token.WHILE: case Token.DO: return false; case Token.TRY: // Consider all code under a try/catch to be conditionally executed. if (!hasFinally(parent) || parent.getLastChild() != n) { return false; } continue inspect; case Token.CASE: case Token.DEFAULT_CASE: return false; case Token.SCRIPT: case Token.FUNCTION: // Done, we've reached the scope root. break inspect; } } while ((n = n.getParent()) != null); return true; } /** * @return An appropriate AST node for the boolean value. */ static Node booleanNode(boolean value) { return value ? IR.trueNode() : IR.falseNode(); } /** * @return An appropriate AST node for the double value. */ static Node numberNode(double value, Node srcref) { Node result; if (Double.isNaN(value)) { result = IR.name("NaN"); } else if (value == Double.POSITIVE_INFINITY) { result = IR.name("Infinity"); } else if (value == Double.NEGATIVE_INFINITY) { result = IR.neg(IR.name("Infinity")); } else { result = IR.number(value); } if (srcref != null) { result.srcrefTree(srcref); } return result; } static boolean isNaN(Node n) { if ((n.isName() && n.getString().equals("NaN")) || (n.getType() == Token.DIV && n.getFirstChild().isNumber() && n.getFirstChild().getDouble() == 0 && n.getLastChild().isNumber() && n.getLastChild().getDouble() == 0)) { return true; } return false; } /** * Given an AST and its copy, map the root node of each scope of main to the * corresponding root node of clone */ public static Map<Node, Node> mapMainToClone(Node main, Node clone) { Preconditions.checkState(main.isEquivalentTo(clone)); Map<Node, Node> mtoc = new HashMap<Node, Node>(); mtoc.put(main, clone); mtocHelper(mtoc, main, clone); return mtoc; } private static void mtocHelper(Map<Node, Node> map, Node main, Node clone) { if (main.isFunction()) { map.put(main, clone); }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> = true; NodeTraversal.traverse(compiler, externs, this); } if (root != null) { inExterns = false; NodeTraversal.traverse(compiler, root, this); } } @Override public void hotSwapScript(Node root, Node originalRoot) { Preconditions.checkNotNull(root); Preconditions.checkState(root.isScript()); inExterns = false; NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { JSDocInfo docInfo; switch (n.getType()) { // Infer JSDocInfo on types of all type declarations on variables. case Token.NAME: if (parent == null) { return; } // Only allow JSDoc on VARs, function declarations, and assigns. if (!parent.isVar() && !NodeUtil.isFunctionDeclaration(parent) && !(parent.isAssign() && n == parent.getFirstChild())) { return; } // There are four places the doc info could live. // 1) A FUNCTION node. // /** ... */ function f() { ... } // 2) An ASSIGN parent. // /** ... */ x = function () { ... } // 3) A NAME parent. // var x, /** ... */ y = function() { ... } // 4) A VAR gramps. // /** ... */ var x = function() { ... } docInfo = n.getJSDocInfo(); if (docInfo == null && !(parent.isVar() && !parent.hasOneChild())) { docInfo = parent.getJSDocInfo(); } // Try to find the type of the NAME. JSType varType = n.getJSType(); if (varType == null && parent.isFunction()) { varType = parent.getJSType(); } // If we have no type to attach JSDocInfo to, then there's nothing // we can do. if (varType == null || docInfo == null) { return; } // Dereference the type. If the result is not an object, or already // has docs attached, then do nothing. ObjectType objType = dereferenceToObject(varType); if (objType == null || objType.getJSDocInfo() != null) { return; } attachJSDocInfoToNominalTypeOrShape(objType, docInfo, n.getString()); break; case Token.GETPROP: // Infer JSDocInfo on properties. // There are two ways to write doc comments on a property. // // 1) // /** @deprecated */ // obj.prop = ... // // 2) // /** @deprecated */ // obj.prop; if (parent.isExprResult() || (parent.isAssign() && parent.getFirstChild() == n)) { docInfo = n.getJSDocInfo(); if (docInfo == null) { docInfo = parent.getJSDocInfo(); } if (docInfo != null) { ObjectType lhsType = dere

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>ferenceToObject(n.getFirstChild().getJSType()); if (lhsType != null) { // Put the JSDoc in the property slot, if there is one. String propName = n.getLastChild().getString(); if (lhsType.hasOwnProperty(propName)) { lhsType.setPropertyJSDocInfo(propName, docInfo); } // Put the JSDoc in any constructors or function shapes as well. ObjectType propType = dereferenceToObject(lhsType.getPropertyType(propName)); if (propType != null) { attachJSDocInfoToNominalTypeOrShape( propType, docInfo, n.getQualifiedName()); } } } } break; } } /** * Dereferences the given type to an object, or returns null. */ private ObjectType dereferenceToObject(JSType type) { return ObjectType.cast(type == null ? null : type.dereference()); } /** * Handle cases #1 and #3 in the class doc. */ private void attachJSDocInfoToNominalTypeOrShape( ObjectType objType, JSDocInfo docInfo, @Nullable String qName) { if (objType.isConstructor() || objType.isEnumType() || objType.isInterface()) { // Named types. if (objType.hasReferenceName() && objType.getReferenceName().equals(qName)) { objType.setJSDocInfo(docInfo); if (objType.isConstructor() || objType.isInterface()) { JSType.toMaybeFunctionType(objType).getInstanceType().setJSDocInfo( docInfo); } else if (objType instanceof EnumType) { ((EnumType) objType).getElementsType().setJSDocInfo(docInfo); } } } else if (!objType.isNativeObjectType() && objType.isFunctionType()) { // Structural functions. objType.setJSDocInfo(docInfo); } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> @param properties A map of all the properties of this record type. * @param declared Whether this is a declared or synthesized type. * A synthesized record type is just used for bookkeeping * in the type system. A declared record type was actually used in the * user's program. * @throws IllegalStateException if the {@code RecordProperty} associated * with a property is null. */ RecordType(JSTypeRegistry registry, Map<String, RecordProperty> properties, boolean declared) { super(registry, null, null); setPrettyPrint(true); this.declared = declared; for (String property : properties.keySet()) { RecordProperty prop = properties.get(property); if (prop == null) { throw new IllegalStateException( "RecordProperty associated with a property should not be null!"); } if (declared) { defineDeclaredProperty( property, prop.getType(), prop.getPropertyNode()); } else { defineSynthesizedProperty( property, prop.getType(), prop.getPropertyNode()); } } // Freeze the record type. isFrozen = true; } /** @return Is this synthesized for internal bookkeeping? */ boolean isSynthetic() { return !declared; } boolean checkRecordEquivalenceHelper( RecordType otherRecord, EquivalenceMethod eqMethod) { Set<String> keySet = getOwnPropertyNames(); Set<String> otherKeySet = otherRecord.getOwnPropertyNames(); if (!otherKeySet.equals(keySet)) { return false; } for (String key : keySet) { if (!otherRecord.getPropertyType(key).checkEquivalenceHelper( getPropertyType(key), eqMethod)) { return false; } } return true; } @Override public ObjectType getImplicitPrototype() { return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE); } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode) { if (isFrozen) { return false; } return super.defineProperty(propertyName, type, inferred, propertyNode); } JSType getGreatestSubtypeHelper(JSType that) { if (that.isRecordType()) { RecordType thatRecord = that.toMaybeRecordType(); RecordTypeBuilder builder = new RecordTypeBuilder(registry); builder.setSynthesized(true); // The greatest subtype consists of those *unique* properties of both // record types. If any property conflicts, then the NO_TYPE type // is returned. for (String property : getOwnPropertyNames()) { if (thatRecord.hasProperty(property) && !thatRecord.getPropertyType(property).isInvariant( getPropertyType(property))) { return registry.getNativeObjectType(JSTypeNative.NO_TYPE); } builder.addProperty(property, getPropertyType(property), getPropertyNode(property)); } for (String property : thatRecord.getOwnPropertyNames()) { if (!hasProperty(property)) { builder.addProperty(property, thatRecord.getPropertyType(property), thatRecord.getPropertyNode(property)); } } return builder.build(); } JSType greatestSubtype = registry.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import com.google.javascript.rhino.ErrorReporter; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; import com.google.javascript.rhino.jstype.StaticReference; import com.google.javascript.rhino.jstype.StaticScope; import com.google.javascript.rhino.jstype.StaticSlot; import com.google.javascript.rhino.jstype.StaticSourceFile; import com.google.javascript.rhino.jstype.StaticSymbolTable; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; /** * Scope contains information about a variable scope in JavaScript. * Scopes can be nested, a scope points back to its parent scope. * A Scope contains information about variables defined in that scope. * <p> * A Scope is also used as a lattice element for flow-sensitive type inference. * As a lattice element, a Scope is viewed as a map from names to types. A name * not in the map is considered to have the bottom type. The join of two maps m1 * and m2 is the map of the union of names with {@link JSType#getLeastSupertype} * to meet the m1 type and m2 type. * * @see NodeTraversal * @see DataFlowAnalysis * */ public class Scope implements StaticScope<JSType>, StaticSymbolTable<Scope.Var, Scope.Var> { private final Map<String, Var> vars = new LinkedHashMap<String, Var>(); private final Scope parent; private final int depth; private final Node rootNode; /** Whether this is a bottom scope for the purposes of type inference. */ private final boolean isBottom; private Var arguments; private static final Predicate<Var> DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES = new Predicate<Var>() { @Override public boolean apply(Var var) { return var.getParentNode() != null && var.getType() == null && // no declared type var

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.getParentNode().isVar() && !var.isExtern(); } }; /** Stores info about a variable */ public static class Var implements StaticSlot<JSType>, StaticReference<JSType> { /** name */ final String name; /** Var node */ final Node nameNode; /** * The variable's type. */ private JSType type; /** * Whether the variable's type has been inferred or is declared. An inferred * type may change over time (as more code is discovered), whereas a * declared type is a static contract that must be matched. */ private final boolean typeInferred; /** Input source */ final CompilerInput input; /** * The index at which the var is declared. e..g if it's 0, it's the first * declared variable in that scope */ final int index; /** The enclosing scope */ final Scope scope; /** @see #isMarkedEscaped */ private boolean markedEscaped = false; /** @see #isMarkedAssignedExactlyOnce */ private boolean markedAssignedExactlyOnce = false; /** * Creates a variable. * * @param inferred whether its type is inferred (as opposed to declared) */ private Var(boolean inferred, String name, Node nameNode, JSType type, Scope scope, int index, CompilerInput input) { this.name = name; this.nameNode = nameNode; this.type = type; this.scope = scope; this.index = index; this.input = input; this.typeInferred = inferred; } /** * Gets the name of the variable. */ @Override public String getName() { return name; } /** * Gets the node for the name of the variable. */ @Override public Node getNode() { return nameNode; } CompilerInput getInput() { return input; } @Override public StaticSourceFile getSourceFile() { return nameNode.getStaticSourceFile(); } @Override public Var getSymbol() { return this; } @Override public Var getDeclaration() { return nameNode == null ? null : this; } /** * Gets the parent of the name node. */ public Node getParentNode() { return nameNode == null ? null : nameNode.getParent(); } /** * Whether this is a bleeding function (an anonymous named function * that bleeds into the inner scope). */ public boolean isBleedingFunction() { return NodeUtil.isFunctionExpression(getParentNode()); } /** * Gets the scope where this variable is declared. */ Scope getScope() { return scope; } /** * Returns whether this is a global variable. */ public boolean isGlobal() { return scope.isGlobal(); } /** * Returns whether this is a local variable. */ public boolean isLocal() { return scope.isLocal(); } /** * Returns whether this is defined in an extern file. */ boolean isExtern() { return input == null || input.isExtern(); } /** * Returns {@code true} if the variable is declared as a constant

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>, * based on the value reported by {@code NodeUtil}. */ public boolean isConst() { return nameNode != null && NodeUtil.isConstantName(nameNode); } /** * Returns {@code true} if the variable is declared as a define. * A variable is a define if it is annotated by {@code @define}. */ public boolean isDefine() { JSDocInfo info = getJSDocInfo(); return info != null && info.isDefine(); } public Node getInitialValue() { return NodeUtil.getRValueOfLValue(nameNode); } /** * Gets this variable's type. To know whether this type has been inferred, * see {@code #isTypeInferred()}. */ @Override public JSType getType() { return type; } /** * Returns the name node that produced this variable. */ public Node getNameNode() { return nameNode; } /** * Gets the JSDocInfo for the variable. */ @Override public JSDocInfo getJSDocInfo() { return nameNode == null ? null : NodeUtil.getBestJSDocInfo(nameNode); } /** * Sets this variable's type. * @throws IllegalStateException if the variable's type is not inferred */ void setType(JSType type) { Preconditions.checkState(isTypeInferred()); this.type = type; } /** * Resolve this variable's type. */ void resolveType(ErrorReporter errorReporter) { if (type != null) { type = type.resolve(errorReporter, scope); } } /** * Returns whether this variable's type is inferred. To get the variable's * type, see {@link #getType()}. */ @Override public boolean isTypeInferred() { return typeInferred; } public String getInputName() { if (input == null) { return "<non-file>"; } return input.getName(); } public boolean isNoShadow() { JSDocInfo info = getJSDocInfo(); return info != null && info.isNoShadow(); } @Override public boolean equals(Object other) { if (!(other instanceof Var)) { return false; } Var otherVar = (Var) other; return otherVar.nameNode == nameNode; } @Override public int hashCode() { return nameNode.hashCode(); } @Override public String toString() { return "Scope.Var " + name + "{" + type + "}"; } /** * Record that this is escaped by an inner scope. * * In other words, it's assigned in an inner scope so that it's much harder * to make assertions about its value at a given point. */ void markEscaped() { markedEscaped = true; } /** * Whether this is escaped by an inner scope. * Notice that not all scope creators record this information. */ boolean isMarkedEscaped() { return markedEscaped; } /** * Record that this is assigned exactly once.. * * In other words, it's assigned in an inner scope so that it's much

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> harder * to make assertions about its value at a given point. */ void markAssignedExactlyOnce() { markedAssignedExactlyOnce = true; } /** * Whether this is assigned exactly once. * Notice that not all scope creators record this information. */ boolean isMarkedAssignedExactlyOnce() { return markedAssignedExactlyOnce; } } /** * A special subclass of Var used to distinguish "arguments" in the current * scope. */ // TODO(johnlenz): Include this the list of Vars for the scope. public static class Arguments extends Var { Arguments(Scope scope) { super( false, // no inferred "arguments", // always arguments null, // no declaration node // TODO(johnlenz): provide the type of "Arguments". null, // no type info scope, -1, // no variable index null // input ); } @Override public boolean equals(Object other) { if (!(other instanceof Arguments)) { return false; } Arguments otherVar = (Arguments) other; return otherVar.scope.getRootNode() == scope.getRootNode(); } @Override public int hashCode() { return System.identityHashCode(this); } } /** * Creates a Scope given the parent Scope and the root node of the scope. * @param parent The parent Scope. Cannot be null. * @param rootNode Typically the FUNCTION node. */ Scope(Scope parent, Node rootNode) { Preconditions.checkNotNull(parent); Preconditions.checkArgument(rootNode != parent.rootNode); this.parent = parent; this.rootNode = rootNode; this.isBottom = false; this.depth = parent.depth + 1; } /** * Creates a empty Scope (bottom of the lattice). * @param rootNode Typically a FUNCTION node or the global BLOCK node. * @param isBottom Whether this is the bottom of a lattice. Otherwise, * it must be a global scope. */ private Scope(Node rootNode, boolean isBottom) { this.parent = null; this.rootNode = rootNode; this.isBottom = isBottom; this.depth = 0; } static Scope createGlobalScope(Node rootNode) { return new Scope(rootNode, false); } static Scope createLatticeBottom(Node rootNode) { return new Scope(rootNode, true); } /** The depth of the scope. The global scope has depth 0. */ int getDepth() { return depth; } /** Whether this is the bottom of the lattice. */ boolean isBottom() { return isBottom; } /** * Gets the container node of the scope. This is typically the FUNCTION * node or the global BLOCK/SCRIPT node. */ @Override public Node getRootNode() { return rootNode; } public Scope getParent() { return parent; } Scope getGlobalScope() { Scope result = this; while (result.getParent() != null) { result = result.getParent(); } return result; } @Override public StaticScope<JSType> getParentScope() { return

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> parent; } /** * Gets the type of {@code this} in the current scope. */ @Override public JSType getTypeOfThis() { if (isGlobal()) { return ObjectType.cast(rootNode.getJSType()); } Preconditions.checkState(rootNode.isFunction()); JSType nodeType = rootNode.getJSType(); if (nodeType != null && nodeType.isFunctionType()) { return nodeType.toMaybeFunctionType().getTypeOfThis(); } else { return parent.getTypeOfThis(); } } /** * Declares a variable whose type is inferred. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. */ Var declare(String name, Node nameNode, JSType type, CompilerInput input) { return declare(name, nameNode, type, input, true); } /** * Declares a variable. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. * @param inferred Whether this variable's type is inferred (as opposed * to declared). */ Var declare(String name, Node nameNode, JSType type, CompilerInput input, boolean inferred) { Preconditions.checkState(name != null && name.length() > 0); // Make sure that it's declared only once Preconditions.checkState(vars.get(name) == null); Var var = new Var(inferred, name, nameNode, type, this, vars.size(), input); vars.put(name, var); return var; } /** * Undeclares a variable, to be used when the compiler optimizes out * a variable and removes it from the scope. */ void undeclare(Var var) { Preconditions.checkState(var.scope == this); Preconditions.checkState(vars.get(var.name) == var); vars.remove(var.name); } @Override public Var getSlot(String name) { return getVar(name); } @Override public Var getOwnSlot(String name) { return vars.get(name); } /** * Returns the variable, may be null */ public Var getVar(String name) { Var var = vars.get(name); if (var != null) { return var; } else if (parent != null) { // Recurse up the parent Scope return parent.getVar(name); } else { return null; } } /** * Get a unique VAR object to represents "arguments" within this scope */ public Var getArgumentsVar() { if (arguments == null) { arguments = new Arguments(this); } return arguments; } /** * Returns true if a variable is declared. */ public boolean isDeclared(String name, boolean recurse) { Scope scope = this; if (scope.vars.containsKey(name)) { return true; } if

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> (scope.parent != null && recurse) { return scope.parent.isDeclared(name, recurse); } return false; } /** * Return an iterator over all of the variables declared in this scope. */ public Iterator<Var> getVars() { return vars.values().iterator(); } /** * Return an iterable over all of the variables declared in this scope. */ Iterable<Var> getVarIterable() { return vars.values(); } @Override public Iterable<Var> getReferences(Var var) { return ImmutableList.of(var); } @Override public StaticScope<JSType> getScope(Var var) { return var.scope; } @Override public Iterable<Var> getAllSymbols() { return Collections.unmodifiableCollection(vars.values()); } /** * Returns number of variables in this scope */ public int getVarCount() { return vars.size(); } /** * Returns whether this is the global scope. */ public boolean isGlobal() { return parent == null; } /** * Returns whether this is a local scope (i.e. not the global scope). */ public boolean isLocal() { return parent != null; } /** * Gets all variables declared with "var" but without declared types attached. */ public Iterator<Var> getDeclarativelyUnboundVarsWithoutTypes() { return Iterators.filter( getVars(), DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES); } static interface TypeResolver { void resolveTypes(); } private TypeResolver typeResolver; /** Resolve all type references. Only used on typed scopes. */ void resolveTypes() { if (typeResolver != null) { typeResolver.resolveTypes(); typeResolver = null; } } void setTypeResolver(TypeResolver resolver) { this.typeResolver = resolver; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { return BooleanLiteralSet.BOTH; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { return this; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { Node callNode = functionCall.callNode; Node parent = callNode.getParent(); if (functionCall.tweakFunc.isGetterFunction()) { Node newValue; if (isRegistered) { newValue = tweakInfo.getDefaultValueNode().cloneNode(); } else { // When we find a getter of an unregistered tweak, there has // already been a warning about it, so now just use a default // value when stripping. TweakFunction registerFunction = functionCall.tweakFunc.registerFunction; newValue = registerFunction.createDefaultValueNode(); } parent.replaceChild(callNode, newValue); } else { Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode)) .srcref(callNode); parent.replaceChild(callNode, voidZeroNode); } } } return !tweakInfos.isEmpty(); } /** * Creates a JS object that holds a map of tweakId -> default value override. */ private Node createCompilerDefaultValueOverridesVarNode( Node sourceInformationNode) { Node objNode = IR.objectlit().srcref(sourceInformationNode); for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) { Node objKeyNode = IR.stringKey(entry.getKey()) .copyInformationFrom(sourceInformationNode); Node objValueNode = entry.getValue().cloneNode() .copyInformationFrom(sourceInformationNode); objKeyNode.addChildToBack(objValueNode); objNode.addChildToBack(objKeyNode); } return objNode; } /** Sets the default values of tweaks based on compiler options. */ private void applyCompilerDefaultValueOverrides( Map<String, TweakInfo> tweakInfos) { for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) { String tweakId = entry.getKey(); TweakInfo tweakInfo = tweakInfos.get(tweakId); if (tweakInfo == null) { compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId)); } else { TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc; Node value = entry.getValue(); if (!registerFunc.isValidNodeType(value.getType())) { compiler.report(JSError.make(INVALID_TWEAK_DEFAULT_VALUE_WARNING, tweakId, registerFunc.getName(), registerFunc.getExpectedTypeName())); } else { tweakInfo.defaultValueNode = value; } } } } /** * Finds all calls to goog.tweak functions and emits warnings/errors if any * of the calls have issues. * @return A map of {@link TweakInfo} structures, keyed by tweak ID. */ private CollectTweaksResult collectTweaks(Node root) { CollectTweaks pass = new CollectTweaks(); NodeTraversal.traverse(compiler, root, pass); Map<String, TweakInfo> tweakInfos = pass.allTweaks; for (TweakInfo tweakInfo : tweakInfos.values()) { tweakInfo.emitAllWarnings(); } return new CollectTweaksResult(tweakInfos, pass.getOverridesCalls);

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>_TWEAK_INIT_ERROR, tweakId)); break; } // Ensure tweak overrides occur before the tweak is registered. if (tweakInfo.isRegistered()) { compiler.report( t.makeError(n, TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR, tweakId)); break; } tweakDefaultValueNode = tweakIdNode.getNext(); tweakInfo.addOverrideDefaultValueCall(t.getSourceName(), tweakFunc, n, tweakDefaultValueNode); break; case GET_BOOLEAN: case GET_NUMBER: case GET_STRING: tweakInfo.addGetterCall(t.getSourceName(), tweakFunc, n); } } } /** * Holds information about a call to a goog.tweak function. */ private static final class TweakFunctionCall { final String sourceName; final TweakFunction tweakFunc; final Node callNode; final Node valueNode; TweakFunctionCall(String sourceName, TweakFunction tweakFunc, Node callNode) { this(sourceName, tweakFunc, callNode, null); } TweakFunctionCall(String sourceName, TweakFunction tweakFunc, Node callNode, Node valueNode) { this.sourceName = sourceName; this.callNode = callNode; this.tweakFunc = tweakFunc; this.valueNode = valueNode; } Node getIdNode() { return callNode.getFirstChild().getNext(); } } /** * Stores information about a single tweak. */ private final class TweakInfo { final String tweakId; final List<TweakFunctionCall> functionCalls; TweakFunctionCall registerCall; Node defaultValueNode; TweakInfo(String tweakId) { this.tweakId = tweakId; functionCalls = Lists.newArrayList(); } /** * If this tweak is registered, then looks for type warnings in default * value parameters and getter functions. If it is not registered, emits an * error for each function call. */ void emitAllWarnings() { if (isRegistered()) { emitAllTypeWarnings(); } else { emitUnknownTweakErrors(); } } /** * Emits a warning for each default value parameter that has the wrong type * and for each getter function that was used for the wrong type of tweak. */ void emitAllTypeWarnings() { for (TweakFunctionCall call : functionCalls) { Node valueNode = call.valueNode; TweakFunction tweakFunc = call.tweakFunc; TweakFunction registerFunc = registerCall.tweakFunc; if (valueNode != null) { // For register* and overrideDefaultValue calls, ensure the default // value is a literal of the correct type. if (!registerFunc.isValidNodeType(valueNode.getType())) { compiler.report(JSError.make(call.sourceName, valueNode, INVALID_TWEAK_DEFAULT_VALUE_WARNING, tweakId, registerFunc.getName(), registerFunc.getExpectedTypeName())); } } else if (tweakFunc.isGetterFunction()) { // For getter calls, ensure the correct getter was used. if (!tweakFunc.isCorrectRegisterFunction(registerFunc)) { compiler

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> name for this infimum. It's equivalent to "elements of this enum that * are numbers". * * The best we can do is make up a new type. This is similar to what * we do in UnionType#meet, which kind-of-sort-of makes sense, because * an EnumElementType is a union of instances of a type. */ JSType meet(JSType that) { JSType meetPrimitive = primitiveType.getGreatestSubtype(that); if (meetPrimitive.isEmptyType()) { return null; } else { return new EnumElementType(registry, meetPrimitive, name); } } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { primitiveType = primitiveType.resolve(t, scope); primitiveObjectType = ObjectType.cast(primitiveType); return this; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(nicksantos): Organizationally, this feels like it should be in Rhino. * But it depends on some coding convention stuff that's really part * of JSCompiler. * * @author nicksantos@google.com (Nick Santos) * @author pascallouis@google.com (Pascal-Louis Perez) */ final class FunctionTypeBuilder { private final String fnName; private final AbstractCompiler compiler; private final CodingConvention codingConvention; private final JSTypeRegistry typeRegistry; private final Node errorRoot; private final String sourceName; private final Scope scope; private FunctionContents contents = UnknownFunctionContents.get(); private JSType returnType = null; private boolean returnTypeInferred = false; private List<ObjectType> implementedInterfaces = null; private List<ObjectType> extendedInterfaces = null; private ObjectType baseType = null; private JSType thisType = null; private boolean isConstructor = false; private boolean makesStructs = false; private boolean makesDicts = false; private boolean isInterface = false; private Node parametersNode = null; private ImmutableList<TemplateType> templateTypeNames = ImmutableList.of(); // TODO(johnlenz): verify we want both template and class template lists instead of a unified // list. private ImmutableList<TemplateType> classTemplateTypeNames = ImmutableList.of(); static final DiagnosticType EXTENDS_WITHOUT_TYPEDEF = DiagnosticType.warning( "JSC_EXTENDS_WITHOUT_TYPEDEF", "@extends used without @constructor or @interface for {0}"); static final DiagnosticType EXTENDS_NON_OBJECT = DiagnosticType.warning( "JSC_EXTENDS_NON_OBJECT", "{0} @extends non-object type {1}"); static final DiagnosticType RESOLVED_TAG_EMPTY = DiagnosticType.warning( "JSC_RESOLVED_TAG_EMPTY", "Could not resolve type in {0} tag of {1}"); static final DiagnosticType IMPLEMENTS_WITHOUT_CONSTRUCTOR = DiagnosticType.warning( "JSC_IMPLEMENTS_WITHOUT_CONSTRUCTOR", "@implements used without @constructor or @interface for {0}"); static final DiagnosticType CONSTRUCTOR_REQUIRED = DiagnosticType.warning("JSC_CONSTRUCTOR_REQUIRED", "{0} used without @constructor for {1}"); static final DiagnosticType VAR_ARGS_MUST_BE_LAST = DiagnosticType.warning( "JSC_VAR_ARGS_MUST_BE_LAST", "variable length argument must be last"); static final DiagnosticType OPTIONAL_ARG_AT_END = DiagnosticType.warning( "JSC_OPTIONAL_ARG_AT_END", "optional arguments must be at the end"); static final DiagnosticType INEXISTANT_PARAM = DiagnosticType.warning( "JSC_INEXISTANT_PARAM", "parameter {0} does not appear in {1}''s parameter list"); static final DiagnosticType TYPE_REDEFINITION = DiagnosticType.warning( "JSC_TYPE_REDEFINITION", "attempted re-definition of type {0}\n" + "found : {1}\n" + "expected: {2}"); static final DiagnosticType TEMPLATE_TYPE_DUPLIC

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>ATED = DiagnosticType.warning( "JSC_TEMPLATE_TYPE_DUPLICATED", "Only one parameter type must be the template type"); static final DiagnosticType TEMPLATE_TYPE_EXPECTED = DiagnosticType.warning( "JSC_TEMPLATE_TYPE_EXPECTED", "The template type must be a parameter type"); static final DiagnosticType THIS_TYPE_NON_OBJECT = DiagnosticType.warning( "JSC_THIS_TYPE_NON_OBJECT", "@this type of a function must be an object\n" + "Actual type: {0}"); static final DiagnosticType SAME_INTERFACE_MULTIPLE_IMPLEMENTS = DiagnosticType.warning( "JSC_SAME_INTERFACE_MULTIPLE_IMPLEMENTS", "Cannot @implement the same interface more than once\n" + "Repeated interface: {0}"); private class ExtendedTypeValidator implements Predicate<JSType> { @Override public boolean apply(JSType type) { ObjectType objectType = ObjectType.cast(type); if (objectType == null) { reportWarning(EXTENDS_NON_OBJECT, formatFnName(), type.toString()); return false; } else if (objectType.isEmptyType()) { reportWarning(RESOLVED_TAG_EMPTY, "@extends", formatFnName()); return false; } else if (objectType.isUnknownType()) { if (hasMoreTagsToResolve(objectType)) { return true; } else { reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName); return false; } } else { return true; } } } private class ImplementedTypeValidator implements Predicate<JSType> { @Override public boolean apply(JSType type) { ObjectType objectType = ObjectType.cast(type); if (objectType == null) { reportError(BAD_IMPLEMENTED_TYPE, fnName); return false; } else if (objectType.isEmptyType()) { reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName); return false; } else if (objectType.isUnknownType()) { if (hasMoreTagsToResolve(objectType)) { return true; } else { reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName); return false; } } else { return true; } } } /** * @param fnName The function name. * @param compiler The compiler. * @param errorRoot The node to associate with any warning generated by * this builder. * @param sourceName A source name for associating any warnings that * we have to emit. * @param scope The syntactic scope. */ FunctionTypeBuilder(String fnName, AbstractCompiler compiler, Node errorRoot, String sourceName, Scope scope) { Preconditions.checkNotNull(errorRoot); this.fnName = fnName == null ? "" : fnName; this.codingConvention = compiler.getCodingConvention(); this.typeRegistry = compiler.getTypeRegistry(); this.errorRoot = errorRoot; this.sourceName = sourceName; this.compiler = compiler; this.scope = scope; } /** Format the function name for use in warnings. */ String formatFnName

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>() { return fnName.isEmpty() ? "<anonymous>" : fnName; } /** * Sets the contents of this function. */ FunctionTypeBuilder setContents(@Nullable FunctionContents contents) { if (contents != null) { this.contents = contents; } return this; } /** * Infer the parameter and return types of a function from * the parameter and return types of the function it is overriding. * * @param oldType The function being overridden. Does nothing if this is null. * @param paramsParent The LP node of the function that we're assigning to. * If null, that just means we're not initializing this to a function * literal. */ FunctionTypeBuilder inferFromOverriddenFunction( @Nullable FunctionType oldType, @Nullable Node paramsParent) { if (oldType == null) { return this; } returnType = oldType.getReturnType(); returnTypeInferred = oldType.isReturnTypeInferred(); if (paramsParent == null) { // Not a function literal. parametersNode = oldType.getParametersNode(); if (parametersNode == null) { parametersNode = new FunctionParamBuilder(typeRegistry).build(); } } else { // We're overriding with a function literal. Apply type information // to each parameter of the literal. FunctionParamBuilder paramBuilder = new FunctionParamBuilder(typeRegistry); Iterator<Node> oldParams = oldType.getParameters().iterator(); boolean warnedAboutArgList = false; boolean oldParamsListHitOptArgs = false; for (Node currentParam = paramsParent.getFirstChild(); currentParam != null; currentParam = currentParam.getNext()) { if (oldParams.hasNext()) { Node oldParam = oldParams.next(); Node newParam = paramBuilder.newParameterFromNode(oldParam); oldParamsListHitOptArgs = oldParamsListHitOptArgs || oldParam.isVarArgs() || oldParam.isOptionalArg(); // The subclass method might write its var_args as individual // arguments. if (currentParam.getNext() != null && newParam.isVarArgs()) { newParam.setVarArgs(false); newParam.setOptionalArg(true); } } else { warnedAboutArgList |= addParameter( paramBuilder, typeRegistry.getNativeType(UNKNOWN_TYPE), warnedAboutArgList, codingConvention.isOptionalParameter(currentParam) || oldParamsListHitOptArgs, codingConvention.isVarArgsParameter(currentParam)); } } // Clone any remaining params that aren't in the function literal, // but make them optional. while (oldParams.hasNext()) { paramBuilder.newOptionalParameterFromNode(oldParams.next()); } parametersNode = paramBuilder.build(); } return this; } /** * Infer the return type from JSDocInfo. */ FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) { if (info != null && info.hasReturnType()) { returnType = info.getReturnType().evaluate(scope, typeRegistry); returnTypeInferred = false; } return this; } /** * Infer the role

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> of the function (whether it's a constructor or interface) * and what it inherits from in JSDocInfo. */ FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) { if (info != null) { isConstructor = info.isConstructor(); makesStructs = info.makesStructs(); makesDicts = info.makesDicts(); isInterface = info.isInterface(); if (makesStructs && !isConstructor) { reportWarning(CONSTRUCTOR_REQUIRED, "@struct", formatFnName()); } else if (makesDicts && !isConstructor) { reportWarning(CONSTRUCTOR_REQUIRED, "@dict", formatFnName()); } // Class template types, which can be used in the scope of a constructor // definition. ImmutableList<String> typeParameters = info.getTemplateTypeNames(); if (!typeParameters.isEmpty()) { if (isConstructor || isInterface) { ImmutableList.Builder<TemplateType> builder = ImmutableList.builder(); for (String typeParameter : typeParameters) { builder.add(typeRegistry.createTemplateType(typeParameter)); } classTemplateTypeNames = builder.build(); typeRegistry.setTemplateTypeNames(classTemplateTypeNames); } } // base type if (info.hasBaseType()) { if (isConstructor) { JSType maybeBaseType = info.getBaseType().evaluate(scope, typeRegistry); if (maybeBaseType != null && maybeBaseType.setValidator(new ExtendedTypeValidator())) { baseType = (ObjectType) maybeBaseType; } } else { reportWarning(EXTENDS_WITHOUT_TYPEDEF, formatFnName()); } } // Implemented interfaces (for constructors only). if (info.getImplementedInterfaceCount() > 0) { if (isConstructor) { implementedInterfaces = Lists.newArrayList(); Set<JSType> baseInterfaces = new HashSet<JSType>(); for (JSTypeExpression t : info.getImplementedInterfaces()) { JSType maybeInterType = t.evaluate(scope, typeRegistry); if (maybeInterType != null && maybeInterType.setValidator(new ImplementedTypeValidator())) { // Disallow implementing the same base (not templatized) interface // type more than once. JSType baseInterface = maybeInterType; if (baseInterface.toMaybeTemplatizedType() != null) { baseInterface = baseInterface.toMaybeTemplatizedType().getReferencedType(); } if (baseInterfaces.contains(baseInterface)) { reportWarning(SAME_INTERFACE_MULTIPLE_IMPLEMENTS, baseInterface.toString()); } else { baseInterfaces.add(baseInterface); } implementedInterfaces.add((ObjectType) maybeInterType); } } } else if (isInterface) { reportWarning( TypeCheck.CONFLICTING_IMPLEMENTED_TYPE, formatFnName()); } else { reportWarning(CONSTRUCTOR_REQUIRED, "@implements", formatFnName()); } } // extended interfaces (for interfaces only) // We've already emitted a warning if this is not an interface. if (isInterface) { extendedInterfaces = Lists.newArrayList(); for (JSTypeExpression t : info.getExtendedInterfaces()) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> JSType maybeInterfaceType = t.evaluate(scope, typeRegistry); if (maybeInterfaceType != null && maybeInterfaceType.setValidator(new ExtendedTypeValidator())) { extendedInterfaces.add((ObjectType) maybeInterfaceType); } } } } return this; } /** * Infers the type of {@code this}. * @param type The type of this if the info is missing. */ FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) { // Look at the @this annotation first. inferThisType(info); if (thisType == null) { ObjectType objType = ObjectType.cast(type); if (objType != null && (info == null || !info.hasType())) { thisType = objType; } } return this; } /** * Infers the type of {@code this}. * @param info The JSDocInfo for this function. */ FunctionTypeBuilder inferThisType(JSDocInfo info) { JSType maybeThisType = null; if (info != null && info.hasThisType()) { // TODO(johnlenz): In ES5 strict mode a function can have a null or // undefined "this" value, but all the existing "@this" annotations // don't declare restricted types. maybeThisType = info.getThisType().evaluate(scope, typeRegistry) .restrictByNotNullOrUndefined(); } if (maybeThisType != null) { thisType = maybeThisType; } return this; } /** * Infer the parameter types from the doc info alone. */ FunctionTypeBuilder inferParameterTypes(JSDocInfo info) { // Create a fake args parent. Node lp = IR.paramList(); for (String name : info.getParameterNames()) { lp.addChildToBack(IR.name(name)); } return inferParameterTypes(lp, info); } /** * Infer the parameter types from the list of argument names and * the doc info. */ FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent, @Nullable JSDocInfo info) { if (argsParent == null) { if (info == null) { return this; } else { return inferParameterTypes(info); } } // arguments Node oldParameterType = null; if (parametersNode != null) { oldParameterType = parametersNode.getFirstChild(); } FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry); boolean warnedAboutArgList = false; Set<String> allJsDocParams = (info == null) ? Sets.<String>newHashSet() : Sets.newHashSet(info.getParameterNames()); boolean isVarArgs = false; for (Node arg : argsParent.children()) { String argumentName = arg.getString(); allJsDocParams.remove(argumentName); // type from JSDocInfo JSType parameterType = null; boolean isOptionalParam = isOptionalParameter(arg, info); isVarArgs = isVarArgsParameter(arg, info); if (info != null && info.hasParameterType(argumentName)) { parameterType = info.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>getParameterType(argumentName).evaluate(scope, typeRegistry); } else if (arg.getJSDocInfo() != null && arg.getJSDocInfo().hasType()) { parameterType = arg.getJSDocInfo().getType().evaluate(scope, typeRegistry); } else if (oldParameterType != null && oldParameterType.getJSType() != null) { parameterType = oldParameterType.getJSType(); isOptionalParam = oldParameterType.isOptionalArg(); isVarArgs = oldParameterType.isVarArgs(); } else { parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE); } warnedAboutArgList |= addParameter( builder, parameterType, warnedAboutArgList, isOptionalParam, isVarArgs); if (oldParameterType != null) { oldParameterType = oldParameterType.getNext(); } } // Copy over any old parameters that aren't in the param list. if (!isVarArgs) { while (oldParameterType != null && !isVarArgs) { builder.newParameterFromNode(oldParameterType); oldParameterType = oldParameterType.getNext(); } } for (String inexistentName : allJsDocParams) { reportWarning(INEXISTANT_PARAM, inexistentName, formatFnName()); } parametersNode = builder.build(); return this; } /** * @return Whether the given param is an optional param. */ private boolean isOptionalParameter( Node param, @Nullable JSDocInfo info) { if (codingConvention.isOptionalParameter(param)) { return true; } String paramName = param.getString(); return info != null && info.hasParameterType(paramName) && info.getParameterType(paramName).isOptionalArg(); } /** * Determine whether this is a var args parameter. * @return Whether the given param is a var args param. */ private boolean isVarArgsParameter( Node param, @Nullable JSDocInfo info) { if (codingConvention.isVarArgsParameter(param)) { return true; } String paramName = param.getString(); return info != null && info.hasParameterType(paramName) && info.getParameterType(paramName).isVarArgs(); } /** * Infer the template type from the doc info. */ FunctionTypeBuilder inferTemplateTypeName( @Nullable JSDocInfo info, JSType ownerType) { if (info != null && !info.getTemplateTypeNames().isEmpty()) { ImmutableList.Builder<TemplateType> builder = ImmutableList.builder(); for (String key : info.getTemplateTypeNames()) { builder.add(typeRegistry.createTemplateType(key)); } templateTypeNames = builder.build(); } else { templateTypeNames = ImmutableList.of(); } ImmutableList<TemplateType> keys = templateTypeNames; if (ownerType != null) { ImmutableList<TemplateType> ownerTypeKeys = ownerType.getTemplateTypeMap().getTemplateKeys(); if (!ownerTypeKeys.isEmpty()) { ImmutableList.Builder<TemplateType> builder = ImmutableList.builder(); builder.addAll(templateTypeNames); builder.addAll(ownerTypeKeys); keys = builder

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>.build(); } } if (!keys.isEmpty()) { typeRegistry.setTemplateTypeNames(keys); } return this; } /** * Add a parameter to the param list. * @param builder A builder. * @param paramType The parameter type. * @param warnedAboutArgList Whether we've already warned about arg ordering * issues (like if optional args appeared before required ones). * @param isOptional Is this an optional parameter? * @param isVarArgs Is this a var args parameter? * @return Whether a warning was emitted. */ private boolean addParameter(FunctionParamBuilder builder, JSType paramType, boolean warnedAboutArgList, boolean isOptional, boolean isVarArgs) { boolean emittedWarning = false; if (isOptional) { // Remembering that an optional parameter has been encountered // so that if a non optional param is encountered later, an // error can be reported. if (!builder.addOptionalParams(paramType) && !warnedAboutArgList) { reportWarning(VAR_ARGS_MUST_BE_LAST); emittedWarning = true; } } else if (isVarArgs) { if (!builder.addVarArgs(paramType) && !warnedAboutArgList) { reportWarning(VAR_ARGS_MUST_BE_LAST); emittedWarning = true; } } else { if (!builder.addRequiredParams(paramType) && !warnedAboutArgList) { // An optional parameter was seen and this argument is not an optional // or var arg so it is an error. if (builder.hasVarArgs()) { reportWarning(VAR_ARGS_MUST_BE_LAST); } else { reportWarning(OPTIONAL_ARG_AT_END); } emittedWarning = true; } } return emittedWarning; } /** * Builds the function type, and puts it in the registry. */ FunctionType buildAndRegister() { if (returnType == null) { // Infer return types. // We need to be extremely conservative about this, because of two // competing needs. // 1) If we infer the return type of f too widely, then we won't be able // to assign f to other functions. // 2) If we infer the return type of f too narrowly, then we won't be // able to override f in subclasses. // So we only infer in cases where the user doesn't expect to write // @return annotations--when it's very obvious that the function returns // nothing. if (!contents.mayHaveNonEmptyReturns() && !contents.mayHaveSingleThrow() && !contents.mayBeFromExterns()) { returnType = typeRegistry.getNativeType(VOID_TYPE); returnTypeInferred = true; } } if (returnType == null) { returnType = typeRegistry.getNativeType(UNKNOWN_TYPE); } if (parametersNode == null) { throw new IllegalStateException( "All Function types must have params and a return type"); } FunctionType fnType; if (isConstructor) { fnType = getOrCreateConstructor(); } else if (isInterface)

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { fnType = typeRegistry.createInterfaceType( fnName, contents.getSourceNode(), classTemplateTypeNames); if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) { typeRegistry.declareType(fnName, fnType.getInstanceType()); } maybeSetBaseType(fnType); } else { fnType = new FunctionBuilder(typeRegistry) .withName(fnName) .withSourceNode(contents.getSourceNode()) .withParamsNode(parametersNode) .withReturnType(returnType, returnTypeInferred) .withTypeOfThis(thisType) .withTemplateKeys(templateTypeNames) .build(); maybeSetBaseType(fnType); } if (implementedInterfaces != null) { fnType.setImplementedInterfaces(implementedInterfaces); } if (extendedInterfaces != null) { fnType.setExtendedInterfaces(extendedInterfaces); } typeRegistry.clearTemplateTypeNames(); return fnType; } private void maybeSetBaseType(FunctionType fnType) { if (!fnType.isInterface() && baseType != null) { fnType.setPrototypeBasedOn(baseType); fnType.extendTemplateTypeMapBasedOn(baseType); } } /** * Returns a constructor function either by returning it from the * registry if it exists or creating and registering a new type. If * there is already a type, then warn if the existing type is * different than the one we are creating, though still return the * existing function if possible. The primary purpose of this is * that registering a constructor will fail for all built-in types * that are initialized in {@link JSTypeRegistry}. We a) want to * make sure that the type information specified in the externs file * matches what is in the registry and b) annotate the externs with * the {@link JSType} from the registry so that there are not two * separate JSType objects for one type. */ private FunctionType getOrCreateConstructor() { FunctionType fnType = typeRegistry.createConstructorType( fnName, contents.getSourceNode(), parametersNode, returnType, classTemplateTypeNames); JSType existingType = typeRegistry.getType(fnName); if (makesStructs) { fnType.setStruct(); } else if (makesDicts) { fnType.setDict(); } if (existingType != null) { boolean isInstanceObject = existingType.isInstanceType(); if (isInstanceObject || fnName.equals("Function")) { FunctionType existingFn = isInstanceObject ? existingType.toObjectType().getConstructor() : typeRegistry.getNativeFunctionType(FUNCTION_FUNCTION_TYPE); if (existingFn.getSource() == null) { existingFn.setSource(contents.getSourceNode()); } if (!existingFn.hasEqualCallType(fnType)) { reportWarning(TYPE_REDEFINITION, formatFnName(), fnType.toString(), existingFn.toString()); } return existingFn; } else { // We fall through and return the created type, even though it will fail // to register. We have no choice as we have to return a function. We // issue an error elsewhere though, so the

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> user should fix it. } } maybeSetBaseType(fnType); if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) { typeRegistry.declareType(fnName, fnType.getInstanceType()); } return fnType; } private void reportWarning(DiagnosticType warning, String ... args) { compiler.report(JSError.make(sourceName, errorRoot, warning, args)); } private void reportError(DiagnosticType error, String ... args) { compiler.report(JSError.make(sourceName, errorRoot, error, args)); } /** * Determines whether the given JsDoc info declares a function type. */ static boolean isFunctionTypeDeclaration(JSDocInfo info) { return info.getParameterCount() > 0 || info.hasReturnType() || info.hasThisType() || info.isConstructor() || info.isInterface(); } /** * The scope that we should declare this function in, if it needs * to be declared in a scope. Notice that TypedScopeCreator takes * care of most scope-declaring. */ private Scope getScopeDeclaredIn() { int dotIndex = fnName.indexOf("."); if (dotIndex != -1) { String rootVarName = fnName.substring(0, dotIndex); Var rootVar = scope.getVar(rootVarName); if (rootVar != null) { return rootVar.getScope(); } } return scope; } /** * Check whether a type is resolvable in the future * If this has a supertype that hasn't been resolved yet, then we can assume * this type will be OK once the super type resolves. * @param objectType * @return true if objectType is resolvable in the future */ private static boolean hasMoreTagsToResolve(ObjectType objectType) { Preconditions.checkArgument(objectType.isUnknownType()); if (objectType.getImplicitPrototype() != null) { // constructor extends class if (objectType.getImplicitPrototype().isResolved()) { return false; } else { return true; } } else { // interface extends interfaces FunctionType ctor = objectType.getConstructor(); if (ctor != null) { for (ObjectType interfaceType : ctor.getExtendedInterfaces()) { if (!interfaceType.isResolved()) { return true; } } } return false; } } /** Holds data dynamically inferred about functions. */ static interface FunctionContents { /** Returns the source node of this function. May be null. */ Node getSourceNode(); /** Returns if the function may be in externs. */ boolean mayBeFromExterns(); /** Returns if a return of a real value (not undefined) appears. */ boolean mayHaveNonEmptyReturns(); /** Returns if this consists of a single throw. */ boolean mayHaveSingleThrow(); /** Gets a list of variables in this scope that are escaped. */ Iterable<String> getEscapedVarNames(); /** Gets a list of variables whose properties are escaped. */ Set<String> getEscapedQualifiedNames(); /** Gets the number of times each variable has been assigned. */ Multiset

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>addAll( convention.getIndirectlyDeclaredProperties()); for (Name name : namespace.getNameForest()) { // Skip extern names. Externs are often not runnable as real code, // and will do things like: // var x; // x.method; // which this check forbids. if (name.inExterns) { continue; } checkDescendantNames(name, name.globalSets + name.localSets > 0); } } private void findPrototypeProps(String type, Set<String> props) { Name slot = namespace.getSlot(type); if (slot != null) { for (Ref ref : slot.getRefs()) { if (ref.type == Ref.Type.PROTOTYPE_GET) { Node fullName = ref.getNode().getParent().getParent(); if (fullName.isGetProp()) { props.add(fullName.getLastChild().getString()); } } } } } /** * Checks to make sure all the descendants of a name are defined if they * are referenced. * * @param name A global name. * @param nameIsDefined If true, {@code name} is defined. Otherwise, it's * undefined, and any references to descendant names should emit warnings. */ private void checkDescendantNames(Name name, boolean nameIsDefined) { if (name.props != null) { for (Name prop : name.props) { // if the ancestor of a property is not defined, then we should emit // warnings for all references to the property. boolean propIsDefined = false; if (nameIsDefined) { // if the ancestor of a property is defined, then let's check that // the property is also explicitly defined if it needs to be. propIsDefined = (!propertyMustBeInitializedByFullName(prop) || prop.globalSets + prop.localSets > 0); } validateName(prop, propIsDefined); checkDescendantNames(prop, propIsDefined); } } } private void validateName(Name name, boolean isDefined) { // If the name is not defined, emit warnings for each reference. While // we're looking through each reference, check all the module dependencies. Ref declaration = name.getDeclaration(); Name parent = name.parent; JSModuleGraph moduleGraph = compiler.getModuleGraph(); for (Ref ref : name.getRefs()) { // Don't worry about global exprs. boolean isGlobalExpr = ref.getNode().getParent().isExprResult(); if (!isDefined && !isTypedef(ref)) { if (!isGlobalExpr) { reportRefToUndefinedName(name, ref); } } else if (declaration != null && ref.getModule() != declaration.getModule() && !moduleGraph.dependsOn( ref.getModule(), declaration.getModule())) { reportBadModuleReference(name, ref); } else { // Check for late references. if (ref.scope.isGlobal()) { // Prototype references are special, because in our reference graph, // A.prototype counts as a reference to A. boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.type; import com.google.javascript.jscomp.graph.LatticeElement; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.StaticScope; import com.google.javascript.rhino.jstype.StaticSlot; /** * A symbol table for inferring types during data flow analysis. * * Each flow scope represents the types of all variables in the scope at * a particular point in the flow analysis. * * @author nicksantos@google.com (Nick Santos) */ public interface FlowScope extends StaticScope<JSType>, LatticeElement { /** * Creates a child of this flow scope, to represent an instruction * directly following this one. */ FlowScope createChildFlowScope(); /** * Defines the type of a symbol at this point in the flow. * @throws IllegalArgumentException If no slot for this symbol exists. */ void inferSlotType(String symbol, JSType type); /** * Infer the type of a qualified name. * * When traversing the control flow of a function, simple names are * declared at the bottom of the flow lattice. But there are far too many * qualified names to be able to do this and be performant. So the bottoms * of qualified names are declared lazily. * * Therefore, when inferring a qualified slot, we need both the "bottom" * type of the slot when we enter the scope, and the current type being * inferred. */ void inferQualifiedSlot(Node node, String symbol, JSType bottomType, JSType inferredType); /** * Optimize this scope and return a new FlowScope with faster lookup. */ FlowScope optimize(); /** * Tries to find a unique refined variable in the refined scope, up to the * the blind scope. * @param blindScope The scope before the refinement, i.e. some parent of the * this scope or itself. * @return The unique refined variable if found or null. */ StaticSlot<JSType> findUniqueRefinedSlot(FlowScope blindScope); /** * Look through the given scope, and try to find slots where it doesn't * have enough type information. Then fill in that type information * with stuff that we've inferred in the local flow. */ void completeScope(StaticScope<JSType> scope); }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> // and return types of G.prototype.constructor. childCtor.cloneWithoutArrowType(), childCtor.getSource()); } } /** * {@inheritDoc} * * <p>Understands several different inheritance patterns that occur in * Google code (various uses of {@code inherits} and {@code mixin}). */ @Override public SubclassRelationship getClassesDefinedByCall(Node callNode) { SubclassRelationship relationship = super.getClassesDefinedByCall(callNode); if (relationship != null) { return relationship; } Node callName = callNode.getFirstChild(); SubclassType type = typeofClassDefiningName(callName); if (type != null) { Node subclass = null; Node superclass = callNode.getLastChild(); // There are six possible syntaxes for a class-defining method: // SubClass.inherits(SuperClass) // goog.inherits(SubClass, SuperClass) // goog$inherits(SubClass, SuperClass) // SubClass.mixin(SuperClass.prototype) // goog.mixin(SubClass.prototype, SuperClass.prototype) // goog$mixin(SubClass.prototype, SuperClass.prototype) boolean isDeprecatedCall = callNode.getChildCount() == 2 && callName.isGetProp(); if (isDeprecatedCall) { // SubClass.inherits(SuperClass) subclass = callName.getFirstChild(); } else if (callNode.getChildCount() == 3) { // goog.inherits(SubClass, SuperClass) subclass = callName.getNext(); } else { return null; } if (type == SubclassType.MIXIN) { // Only consider mixins that mix two prototypes as related to // inheritance. if (!endsWithPrototype(superclass)) { return null; } if (!isDeprecatedCall) { if (!endsWithPrototype(subclass)) { return null; } // Strip off the prototype from the name. subclass = subclass.getFirstChild(); } superclass = superclass.getFirstChild(); } // bail out if either of the side of the "inherits" // isn't a real class name. This prevents us from // doing something weird in cases like: // goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2) if (subclass != null && subclass.isUnscopedQualifiedName() && superclass.isUnscopedQualifiedName()) { return new SubclassRelationship(type, subclass, superclass); } } return null; } /** * Determines whether the given node is a class-defining name, like * "inherits" or "mixin." * @return The type of class-defining name, or null. */ private SubclassType typeofClassDefiningName(Node callName) { // Check if the method name matches one of the class-defining methods. String methodName = null; if (callName.isGetProp()) { methodName = callName.getLastChild().getString(); } else if (callName.isName()) { String name = callName.getString(); int dollarIndex = name.lastIndexOf('$'); if (dollarIndex != -1) { methodName = name.substring(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>dollarIndex + 1); } } if (methodName != null) { if (methodName.equals("inherits")) { return SubclassType.INHERITS; } else if (methodName.equals("mixin")) { return SubclassType.MIXIN; } } return null; } @Override public boolean isSuperClassReference(String propertyName) { return "superClass_".equals(propertyName) || super.isSuperClassReference(propertyName); } /** * Given a qualified name node, returns whether "prototype" is at the end. * For example: * a.b.c => false * a.b.c.prototype => true */ private boolean endsWithPrototype(Node qualifiedName) { return qualifiedName.isGetProp() && qualifiedName.getLastChild().getString().equals("prototype"); } /** * Extracts X from goog.provide('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfProvide(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.provide"); } /** * Extracts X from goog.require('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfRequire(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.require"); } private static String extractClassNameIfGoog(Node node, Node parent, String functionName){ String className = null; if (NodeUtil.isExprCall(parent)) { Node callee = node.getFirstChild(); if (callee != null && callee.isGetProp()) { String qualifiedName = callee.getQualifiedName(); if (functionName.equals(qualifiedName)) { Node target = callee.getNext(); if (target != null && target.isString()) { className = target.getString(); } } } } return className; } /** * Use closure's implementation. * @return closure's function name for exporting properties. */ @Override public String getExportPropertyFunction() { return "goog.exportProperty"; } /** * Use closure's implementation. * @return closure's function name for exporting symbols. */ @Override public String getExportSymbolFunction() { return "goog.exportSymbol"; } @Override public List<String> identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); if ("goog.addDependency".equals(callName.getQualifiedName()) && n.getChildCount() >= 3) { Node typeArray = callName.getNext().getNext(); if (typeArray.isArrayLit()) { List<String> typeNames = Lists.newArrayList(); for (Node name = typeArray.getFirstChild(); name != null; name = name.getNext()) { if (name.isString()) { typeNames.add(name.getString()); } } return typeNames; } } return super.identifyTypeDeclarationCall(n); } @Override public String

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>_INSTANCE_TYPE), new AssertionFunctionSpec("goog.asserts.assertObject", JSTypeNative.OBJECT_TYPE), new AssertionFunctionSpec("goog.asserts.assertArray", JSTypeNative.ARRAY_TYPE), new AssertInstanceofSpec("goog.asserts.assertInstanceof") ); } @Override public Bind describeFunctionBind(Node n, boolean useTypeInfo) { Bind result = super.describeFunctionBind(n, useTypeInfo); if (result != null) { return result; } if (!n.isCall()) { return null; } Node callTarget = n.getFirstChild(); String name = callTarget.getQualifiedName(); if (name != null) { if (name.equals("goog.bind") || name.equals("goog$bind")) { // goog.bind(fn, self, args...); Node fn = callTarget.getNext(); if (fn == null) { return null; } Node thisValue = safeNext(fn); Node parameters = safeNext(thisValue); return new Bind(fn, thisValue, parameters); } if (name.equals("goog.partial") || name.equals("goog$partial")) { // goog.partial(fn, args...); Node fn = callTarget.getNext(); if (fn == null) { return null; } Node thisValue = null; Node parameters = safeNext(fn); return new Bind(fn, thisValue, parameters); } } return null; } @Override public Collection<String> getIndirectlyDeclaredProperties() { return indirectlyDeclaredProperties; } private Node safeNext(Node n) { if (n != null) { return n.getNext(); } return null; } /** * A function that will throw an exception when if the value is not * an instanceof a specific type. */ public static class AssertInstanceofSpec extends AssertionFunctionSpec { public AssertInstanceofSpec(String functionName) { super(functionName, JSTypeNative.OBJECT_TYPE); } /** * Returns the type for a type assertion, or null if the function asserts * that the node must not be null or undefined. */ @Override public JSType getAssertedType(Node call, JSTypeRegistry registry) { if (call.getChildCount() > 2) { Node constructor = call.getFirstChild().getNext().getNext(); if (constructor != null) { JSType ownerType = constructor.getJSType(); if (ownerType != null && ownerType.isFunctionType() && ownerType.isConstructor()) { FunctionType functionType = ((FunctionType) ownerType); return functionType.getInstanceType(); } } } return super.getAssertedType(call, registry); } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> If the property is not defined, this was not a quoted key. The * QUOTED_PROP int property is only assigned to STRING tokens used as * object lit keys. * @return true if this was a quoted string key in an object literal. */ @Override public boolean isQuotedString() { return getBooleanProp(QUOTED_PROP); } /** * This should only be called for STRING nodes created in object lits. */ @Override public void setQuotedString() { putBooleanProp(QUOTED_PROP, true); } private String str; } // PropListItems must be immutable so that they can be shared. private interface PropListItem { int getType(); PropListItem getNext(); PropListItem chain(PropListItem next); Object getObjectValue(); int getIntValue(); } private abstract static class AbstractPropListItem implements PropListItem, Serializable { private static final long serialVersionUID = 1L; private final PropListItem next; private final int propType; AbstractPropListItem(int propType, PropListItem next) { this.propType = propType; this.next = next; } @Override public int getType() { return propType; } @Override public PropListItem getNext() { return next; } @Override public abstract PropListItem chain(PropListItem next); } // A base class for Object storing props private static class ObjectPropListItem extends AbstractPropListItem { private static final long serialVersionUID = 1L; private final Object objectValue; ObjectPropListItem(int propType, Object objectValue, PropListItem next) { super(propType, next); this.objectValue = objectValue; } @Override public int getIntValue() { throw new UnsupportedOperationException(); } @Override public Object getObjectValue() { return objectValue; } @Override public String toString() { return objectValue == null ? "null" : objectValue.toString(); } @Override public PropListItem chain(PropListItem next) { return new ObjectPropListItem(getType(), objectValue, next); } } // A base class for int storing props private static class IntPropListItem extends AbstractPropListItem { private static final long serialVersionUID = 1L; final int intValue; IntPropListItem(int propType, int intValue, PropListItem next) { super(propType, next); this.intValue = intValue; } @Override public int getIntValue() { return intValue; } @Override public Object getObjectValue() { throw new UnsupportedOperationException(); } @Override public String toString() { return String.valueOf(intValue); } @Override public PropListItem chain(PropListItem next) { return new IntPropListItem(getType(), intValue, next); } } public Node(int nodeType) { type = nodeType; parent = null; sourcePosition = -1; } public Node(int nodeType, Node child) { Preconditions.checkArgument(child.parent == null, "new child has existing parent"); Preconditions.checkArgument(child.next == null, "new child has existing sibling"); type = nodeType

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> this(nodeType, left, mid, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node mid2, Node right, int lineno, int charno) { this(nodeType, left, mid, mid2, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children, int lineno, int charno) { this(nodeType, children); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children) { this.type = nodeType; parent = null; if (children.length != 0) { this.first = children[0]; this.last = children[children.length - 1]; for (int i = 1; i < children.length; i++) { if (null != children[i - 1].next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } children[i - 1].next = children[i]; Preconditions.checkArgument(children[i - 1].parent == null); children[i - 1].parent = this; } Preconditions.checkArgument(children[children.length - 1].parent == null); children[children.length - 1].parent = this; if (null != this.last.next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } } } public static Node newNumber(double number) { return new NumberNode(number); } public static Node newNumber(double number, int lineno, int charno) { return new NumberNode(number, lineno, charno); } public static Node newString(String str) { return new StringNode(Token.STRING, str); } public static Node newString(int type, String str) { return new StringNode(type, str); } public static Node newString(String str, int lineno, int charno) { return new StringNode(Token.STRING, str, lineno, charno); } public static Node newString(int type, String str, int lineno, int charno) { return new StringNode(type, str, lineno, charno); } public int getType() { return type; } public void setType(int type) { this.type = type; } public boolean hasChildren() { return first != null; } public Node getFirstChild() { return first; } public Node getLastChild() { return last; } public Node getNext() { return next; } public Node getChildBefore(Node child) { if (child == first) { return null; } Node n = first; while (n.next != child) { n = n.next; if (n == null) { throw new RuntimeException("node is not a child"); } } return n; } public Node getChildAtIndex(int i) { Node n = first; while

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(); if (node != null) { Node oldNext = node.next; node.next = children; lastSibling.next = oldNext; if (node == last) { last = lastSibling; } } else { // Append to the beginning. if (first != null) { lastSibling.next = first; } else { last = lastSibling; } first = children; } } /** * Detach a child from its parent and siblings. */ public void removeChild(Node child) { Node prev = getChildBefore(child); if (prev == null) { first = first.next; } else { prev.next = child.next; } if (child == last) { last = prev; } child.next = null; child.parent = null; } /** * Detaches child from Node and replaces it with newChild. */ public void replaceChild(Node child, Node newChild) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(child); newChild.next = child.next; newChild.parent = this; if (child == first) { first = newChild; } else { Node prev = getChildBefore(child); prev.next = newChild; } if (child == last) { last = newChild; } child.next = null; child.parent = null; } public void replaceChildAfter(Node prevChild, Node newChild) { Preconditions.checkArgument(prevChild.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(prevChild); Node child = prevChild.next; newChild.next = child.next; newChild.parent = this; prevChild.next = newChild; if (child == last) { last = newChild; } child.next = null; child.parent = null; } @VisibleForTesting PropListItem lookupProperty(int propType) { PropListItem x = propListHead; while (x != null && propType != x.getType()) { x = x.getNext(); } return x; } /** * Clone the properties from the provided node without copying * the property object. The receiving node may not have any * existing properties. * @param other The node to clone properties from. * @return this node. */ public Node clonePropsFrom(Node other) { Preconditions.checkState(this.propListHead == null, "Node has existing properties."); this.propListHead = other.propListHead; return this; } public void removeProp(int propType) {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> PropListItem result = removeProp(propListHead, propType); if (result != propListHead) { propListHead = result; } } /** * @param item The item to inspect * @param propType The property to look for * @return The replacement list if the property was removed, or * 'item' otherwise. */ private PropListItem removeProp(PropListItem item, int propType) { if (item == null) { return null; } else if (item.getType() == propType) { return item.getNext(); } else { PropListItem result = removeProp(item.getNext(), propType); if (result != item.getNext()) { return item.chain(result); } else { return item; } } } public Object getProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { return null; } return item.getObjectValue(); } public boolean getBooleanProp(int propType) { return getIntProp(propType) != 0; } /** * Returns the integer value for the property, or 0 if the property * is not defined. */ public int getIntProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { return 0; } return item.getIntValue(); } public int getExistingIntProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { throw new IllegalStateException("missing prop: " + propType); } return item.getIntValue(); } public void putProp(int propType, Object value) { removeProp(propType); if (value != null) { propListHead = createProp(propType, value, propListHead); } } public void putBooleanProp(int propType, boolean value) { putIntProp(propType, value ? 1 : 0); } public void putIntProp(int propType, int value) { removeProp(propType); if (value != 0) { propListHead = createProp(propType, value, propListHead); } } PropListItem createProp(int propType, Object value, PropListItem next) { return new ObjectPropListItem(propType, value, next); } PropListItem createProp(int propType, int value, PropListItem next) { return new IntPropListItem(propType, value, next); } // Gets all the property types, in sorted order. private int[] getSortedPropTypes() { int count = 0; for (PropListItem x = propListHead; x != null; x = x.getNext()) { count++; } int[] keys = new int[count]; for (PropListItem x = propListHead; x != null; x = x.getNext()) { count--; keys[count] = x.getType(); } Arrays.sort(keys); return keys; } /** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */ public double

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> getDouble() throws UnsupportedOperationException { if (this.getType() == Token.NUMBER) { throw new IllegalStateException( "Number node not created with Node.newNumber"); } else { throw new UnsupportedOperationException(this + " is not a number node"); } } /** * Can only be called when <tt>getType() == Token.NUMBER</tt> * @param value value to set. */ public void setDouble(double value) throws UnsupportedOperationException { if (this.getType() == Token.NUMBER) { throw new IllegalStateException( "Number node not created with Node.newNumber"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } /** Can only be called when node has String context. */ public String getString() throws UnsupportedOperationException { if (this.getType() == Token.STRING) { throw new IllegalStateException( "String node not created with Node.newString"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } /** * Can only be called for a Token.STRING or Token.NAME. * @param value the value to set. */ public void setString(String value) throws UnsupportedOperationException { if (this.getType() == Token.STRING || this.getType() == Token.NAME) { throw new IllegalStateException( "String node not created with Node.newString"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } @Override public String toString() { return toString(true, true, true); } public String toString( boolean printSource, boolean printAnnotations, boolean printType) { StringBuilder sb = new StringBuilder(); toString(sb, printSource, printAnnotations, printType); return sb.toString(); } private void toString( StringBuilder sb, boolean printSource, boolean printAnnotations, boolean printType) { sb.append(Token.name(type)); if (this instanceof StringNode) { sb.append(' '); sb.append(getString()); } else if (type == Token.FUNCTION) { sb.append(' '); // In the case of JsDoc trees, the first child is often not a string // which causes exceptions to be thrown when calling toString or // toStringTree. if (first == null || first.getType() != Token.NAME) { sb.append("<invalid>"); } else { sb.append(first.getString()); } } else if (type == Token.NUMBER) { sb.append(' '); sb.append(getDouble()); } if (printSource) { int lineno = getLineno(); if (lineno != -1) { sb.append(' '); sb.append(lineno); } } if (printAnnotations) { int[] keys = getSortedPropTypes(); for (int i = 0; i < keys.length; i++) { int type = keys[i]; PropListItem x = lookupProperty(type); sb.append(" ["); sb.append(propToString(type)); sb.append(": "); String value; switch (type) { default: value = x.toString(); break; } sb.append

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>tree2: " + diff.nodeB.toStringTree(); } return null; } /** * Compare this node to node2 recursively and return the first pair of nodes * that differs doing a preorder depth-first traversal. Package private for * testing. Returns null if the nodes are equivalent. */ NodeMismatch checkTreeEqualsImpl(Node node2) { if (!isEquivalentTo(node2, false, false, false)) { return new NodeMismatch(this, node2); } NodeMismatch res = null; Node n, n2; for (n = first, n2 = node2.first; res == null && n != null; n = n.next, n2 = n2.next) { if (node2 == null) { throw new IllegalStateException(); } res = n.checkTreeEqualsImpl(n2); if (res != null) { return res; } } return res; } /** * Compare this node to node2 recursively and return the first pair of nodes * that differs doing a preorder depth-first traversal. Package private for * testing. Returns null if the nodes are equivalent. */ NodeMismatch checkTreeTypeAwareEqualsImpl(Node node2) { // Do a non-recursive equivalents check. if (!isEquivalentTo(node2, true, false, false)) { return new NodeMismatch(this, node2); } NodeMismatch res = null; Node n, n2; for (n = first, n2 = node2.first; res == null && n != null; n = n.next, n2 = n2.next) { res = n.checkTreeTypeAwareEqualsImpl(n2); if (res != null) { return res; } } return res; } /** Returns true if this node is equivalent semantically to another */ public boolean isEquivalentTo(Node node) { return isEquivalentTo(node, false, true, false); } /** Checks equivalence without going into inner functions */ public boolean isEquivalentToShallow(Node node) { return isEquivalentTo(node, false, true, true); } /** * Returns true if this node is equivalent semantically to another and * the types are equivalent. */ public boolean isEquivalentToTyped(Node node) { return isEquivalentTo(node, true, true, false); } /** * @param compareJsType Whether to compare the JSTypes of the nodes. * @param recur Whether to compare the children of the current node, if * not only the the count of the children are compared. * @param shallow If true, the method doesn't recur into inner functions. * @return Whether this node is equivalent semantically to the provided node. */ boolean isEquivalentTo( Node node, boolean compareJsType, boolean recur, boolean shallow) { if (type != node.getType() || getChildCount() != node.getChildCount() || this.getClass() != node.getClass()) { return false; } if (compareJsType && !JSType.isEquivalent(jsType, node.getJSType())) { return false; }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> if (type == Token.INC || type == Token.DEC) { int post1 = this.getIntProp(INCRDECR_PROP); int post2 = node.getIntProp(INCRDECR_PROP); if (post1 != post2) { return false; } } else if (type == Token.STRING || type == Token.STRING_KEY) { if (type == Token.STRING_KEY) { int quoted1 = this.getIntProp(QUOTED_PROP); int quoted2 = node.getIntProp(QUOTED_PROP); if (quoted1 != quoted2) { return false; } } int slashV1 = this.getIntProp(SLASH_V); int slashV2 = node.getIntProp(SLASH_V); if (slashV1 != slashV2) { return false; } } else if (type == Token.CALL) { if (this.getBooleanProp(FREE_CALL) != node.getBooleanProp(FREE_CALL)) { return false; } } if (recur) { Node n, n2; for (n = first, n2 = node.first; n != null; n = n.next, n2 = n2.next) { if (!n.isEquivalentTo( n2, compareJsType, !(shallow && n.isFunction()), shallow)) { return false; } } } return true; } /** * This function takes a set of GETPROP nodes and produces a string that is * each property separated by dots. If the node ultimately under the left * sub-tree is not a simple name, this is not a valid qualified name. * * @return a null if this is not a qualified name, or a dot-separated string * of the name and properties. */ public String getQualifiedName() { if (type == Token.NAME) { String name = getString(); return name.isEmpty() ? null : name; } else if (type == Token.GETPROP) { String left = getFirstChild().getQualifiedName(); if (left == null) { return null; } return left + "." + getLastChild().getString(); } else if (type == Token.THIS) { return "this"; } else { return null; } } /** * Returns whether a node corresponds to a simple or a qualified name, such as * <code>x</code> or <code>a.b.c</code> or <code>this.a</code>. */ public boolean isQualifiedName() { switch (getType()) { case Token.NAME: return getString().isEmpty() ? false : true; case Token.THIS: return true; case Token.GETPROP: return getFirstChild().isQualifiedName(); default: return false; } } /** * Returns whether a node corresponds to a simple or a qualified name without * a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code> * . */ public boolean isUnscopedQualifiedName() { switch (getType()) { case Token.NAME:

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> or constructor call's side effect flags. * This property is only meaningful for {@link Token#CALL} and * {@link Token#NEW} nodes. */ public void setSideEffectFlags(int flags) { Preconditions.checkArgument( getType() == Token.CALL || getType() == Token.NEW, "setIsNoSideEffectsCall only supports CALL and NEW nodes, got " + Token.name(getType())); putIntProp(SIDE_EFFECT_FLAGS, flags); } public void setSideEffectFlags(SideEffectFlags flags) { setSideEffectFlags(flags.valueOf()); } /** * Returns the side effects flags for this node. */ public int getSideEffectFlags() { return getIntProp(SIDE_EFFECT_FLAGS); } /** * A helper class for getting and setting the side-effect flags. * @author johnlenz@google.com (John Lenz) */ public static class SideEffectFlags { private int value = Node.SIDE_EFFECTS_ALL; public SideEffectFlags() { } public SideEffectFlags(int value) { this.value = value; } public int valueOf() { return value; } /** All side-effect occur and the returned results are non-local. */ public SideEffectFlags setAllFlags() { value = Node.SIDE_EFFECTS_ALL; return this; } /** No side-effects occur and the returned results are local. */ public SideEffectFlags clearAllFlags() { value = Node.NO_SIDE_EFFECTS | Node.FLAG_LOCAL_RESULTS; return this; } public boolean areAllFlagsSet() { return value == Node.SIDE_EFFECTS_ALL; } /** * Preserve the return result flag, but clear the others: * no global state change, no throws, no this change, no arguments change */ public void clearSideEffectFlags() { value |= Node.NO_SIDE_EFFECTS; } public SideEffectFlags setMutatesGlobalState() { // Modify global means everything must be assumed to be modified. removeFlag(Node.FLAG_GLOBAL_STATE_UNMODIFIED); removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED); removeFlag(Node.FLAG_THIS_UNMODIFIED); return this; } public SideEffectFlags setThrows() { removeFlag(Node.FLAG_NO_THROWS); return this; } public SideEffectFlags setMutatesThis() { removeFlag(Node.FLAG_THIS_UNMODIFIED); return this; } public SideEffectFlags setMutatesArguments() { removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED); return this; } public SideEffectFlags setReturnsTainted() { removeFlag(Node.FLAG_LOCAL_RESULTS); return this; } private void removeFlag(int flag) { value &= ~flag; } } /** * @return Whether the only side-effect is "modifies this" */ public boolean isOnlyModifiesThisCall() { return areBitFlagsSet( getSideEffectFlags() & Node.NO_SIDE_EFFECTS, Node.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>FLAG_GLOBAL_STATE_UNMODIFIED | Node.FLAG_ARGUMENTS_UNMODIFIED | Node.FLAG_NO_THROWS); } /** * @return Whether the only side-effect is "modifies arguments" */ public boolean isOnlyModifiesArgumentsCall() { return areBitFlagsSet( getSideEffectFlags() & Node.NO_SIDE_EFFECTS, Node.FLAG_GLOBAL_STATE_UNMODIFIED | Node.FLAG_THIS_UNMODIFIED | Node.FLAG_NO_THROWS); } /** * Returns true if this node is a function or constructor call that * has no side effects. */ public boolean isNoSideEffectsCall() { return areBitFlagsSet(getSideEffectFlags(), NO_SIDE_EFFECTS); } /** * Returns true if this node is a function or constructor call that * returns a primitive or a local object (an object that has no other * references). */ public boolean isLocalResultCall() { return areBitFlagsSet(getSideEffectFlags(), FLAG_LOCAL_RESULTS); } /** Returns true if this is a new/call that may mutate its arguments. */ public boolean mayMutateArguments() { return !areBitFlagsSet(getSideEffectFlags(), FLAG_ARGUMENTS_UNMODIFIED); } /** Returns true if this is a new/call that may mutate global state or throw. */ public boolean mayMutateGlobalStateOrThrow() { return !areBitFlagsSet(getSideEffectFlags(), FLAG_GLOBAL_STATE_UNMODIFIED | FLAG_NO_THROWS); } /** * returns true if all the flags are set in value. */ private boolean areBitFlagsSet(int value, int flags) { return (value & flags) == flags; } /** * This should only be called for STRING nodes children of OBJECTLIT. */ public boolean isQuotedString() { return false; } /** * This should only be called for STRING nodes children of OBJECTLIT. */ public void setQuotedString() { throw new IllegalStateException("not a StringNode"); } static class NodeMismatch { final Node nodeA; final Node nodeB; NodeMismatch(Node nodeA, Node nodeB) { this.nodeA = nodeA; this.nodeB = nodeB; } @Override public boolean equals(Object object) { if (object instanceof NodeMismatch) { NodeMismatch that = (NodeMismatch) object; return that.nodeA.equals(this.nodeA) && that.nodeB.equals(this.nodeB); } return false; } @Override public int hashCode() { return Objects.hashCode(nodeA, nodeB); } } /*** AST type check methods ***/ public boolean isAdd() { return this.getType() == Token.ADD; } public boolean isAnd() { return this.getType() == Token.AND; } public boolean isArrayLit() { return this.getType() == Token.ARRAYLIT; } public boolean isAssign() { return this.getType() == Token.ASSIGN; } public boolean isAssignAdd() { return this.getType()

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> == Token.ASSIGN_ADD; } public boolean isBlock() { return this.getType() == Token.BLOCK; } public boolean isBreak() { return this.getType() == Token.BREAK; } public boolean isCall() { return this.getType() == Token.CALL; } public boolean isCase() { return this.getType() == Token.CASE; } public boolean isCast() { return this.getType() == Token.CAST; } public boolean isCatch() { return this.getType() == Token.CATCH; } public boolean isComma() { return this.getType() == Token.COMMA; } public boolean isContinue() { return this.getType() == Token.CONTINUE; } public boolean isDebugger() { return this.getType() == Token.DEBUGGER; } public boolean isDec() { return this.getType() == Token.DEC; } public boolean isDefaultCase() { return this.getType() == Token.DEFAULT_CASE; } public boolean isDelProp() { return this.getType() == Token.DELPROP; } public boolean isDo() { return this.getType() == Token.DO; } public boolean isEmpty() { return this.getType() == Token.EMPTY; } public boolean isExprResult() { return this.getType() == Token.EXPR_RESULT; } public boolean isFalse() { return this.getType() == Token.FALSE; } public boolean isFor() { return this.getType() == Token.FOR; } public boolean isFunction() { return this.getType() == Token.FUNCTION; } public boolean isGetterDef() { return this.getType() == Token.GETTER_DEF; } public boolean isGetElem() { return this.getType() == Token.GETELEM; } public boolean isGetProp() { return this.getType() == Token.GETPROP; } public boolean isHook() { return this.getType() == Token.HOOK; } public boolean isIf() { return this.getType() == Token.IF; } public boolean isIn() { return this.getType() == Token.IN; } public boolean isInc() { return this.getType() == Token.INC; } public boolean isInstanceOf() { return this.getType() == Token.INSTANCEOF; } public boolean isLabel() { return this.getType() == Token.LABEL; } public boolean isLabelName() { return this.getType() == Token.LABEL_NAME; } public boolean isName() { return this.getType() == Token.NAME; } public boolean isNE() { return this.getType() == Token.NE; } public boolean isNew() { return this.getType() == Token.NEW; } public boolean isNot() { return this.getType() == Token.NOT; } public boolean isNull() { return this.getType() == Token.NULL; } public boolean isNumber() { return this.getType() == Token.NUMBER; } public boolean isObjectLit() { return this.getType() ==

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> Token.OBJECTLIT; } public boolean isOr() { return this.getType() == Token.OR; } public boolean isParamList() { return this.getType() == Token.PARAM_LIST; } public boolean isRegExp() { return this.getType() == Token.REGEXP; } public boolean isReturn() { return this.getType() == Token.RETURN; } public boolean isScript() { return this.getType() == Token.SCRIPT; } public boolean isSetterDef() { return this.getType() == Token.SETTER_DEF; } public boolean isString() { return this.getType() == Token.STRING; } public boolean isStringKey() { return this.getType() == Token.STRING_KEY; } public boolean isSwitch() { return this.getType() == Token.SWITCH; } public boolean isThis() { return this.getType() == Token.THIS; } public boolean isThrow() { return this.getType() == Token.THROW; } public boolean isTrue() { return this.getType() == Token.TRUE; } public boolean isTry() { return this.getType() == Token.TRY; } public boolean isTypeOf() { return this.getType() == Token.TYPEOF; } public boolean isVar() { return this.getType() == Token.VAR; } public boolean isVoid() { return this.getType() == Token.VOID; } public boolean isWhile() { return this.getType() == Token.WHILE; } public boolean isWith() { return this.getType() == Token.WITH; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Maps; import com.google.javascript.jscomp.NodeTraversal.AbstractShallowCallback; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSDocInfo.Visibility; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Map; /** * Insures '@constructor X' has a 'goog.provide("X")' . * */ class CheckProvides implements HotSwapCompilerPass { private final AbstractCompiler compiler; private final CheckLevel checkLevel; private final CodingConvention codingConvention; static final DiagnosticType MISSING_PROVIDE_WARNING = DiagnosticType.disabled( "JSC_MISSING_PROVIDE", "missing goog.provide(''{0}'')"); CheckProvides(AbstractCompiler compiler, CheckLevel checkLevel) { this.compiler = compiler; this.checkLevel = checkLevel; this.codingConvention = compiler.getCodingConvention(); } @Override public void process(Node externs, Node root) { hotSwapScript(root, null); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { CheckProvidesCallback callback = new CheckProvidesCallback(codingConvention); new NodeTraversal(compiler, callback).traverse(scriptRoot); } private class CheckProvidesCallback extends AbstractShallowCallback { private final Map<String, Node> provides = Maps.newHashMap(); private final Map<String, Node> ctors = Maps.newHashMap(); private final CodingConvention convention; CheckProvidesCallback(CodingConvention convention){ this.convention = convention; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: String providedClassName = codingConvention.extractClassNameIfProvide(n, parent); if (providedClassName != null) { provides.put(providedClassName, n); } break; case Token.FUNCTION: visitFunctionNode(n, parent); break; case Token.SCRIPT: visitScriptNode(); } } private void visitFunctionNode(Node n, Node parent) { Node name = null; JSDocInfo info = parent.getJSDocInfo(); if (info != null && info.isConstructor()) { name = parent.getFirstChild(); } else { // look to the

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> child, maybe it's a named function info = n.getJSDocInfo(); if (info != null && info.isConstructor()) { name = n.getFirstChild(); } } if (name != null && name.isQualifiedName()) { String qualifiedName = name.getQualifiedName(); if (!this.convention.isPrivate(qualifiedName)) { Visibility visibility = info.getVisibility(); if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) { ctors.put(qualifiedName, name); } } } } private void visitScriptNode() { for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) { String ctor = ctorEntry.getKey(); int index = -1; boolean found = false; do { index = ctor.indexOf('.', index + 1); String provideKey = index == -1 ? ctor : ctor.substring(0, index); if (provides.containsKey(provideKey)) { found = true; break; } } while (index != -1); if (!found) { Node n = ctorEntry.getValue(); compiler.report( JSError.make(n.getSourceFileName(), n, checkLevel, MISSING_PROVIDE_WARNING, ctorEntry.getKey())); } } provides.clear(); ctors.clear(); } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.javascript.jscomp.Scope.Var; import com.google.javascript.jscomp.type.ClosureReverseAbstractInterpreter; import com.google.javascript.jscomp.type.SemanticReverseAbstractInterpreter; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSTypeNative; import com.google.javascript.rhino.jstype.ObjectType; import com.google.javascript.rhino.testing.Asserts; import java.util.Arrays; import java.util.List; import java.util.Set; /** * Tests {@link TypeCheck}. * */ public class TypeCheckTest extends CompilerTypeTestCase { private CheckLevel reportMissingOverrides = CheckLevel.WARNING; private static final String SUGGESTION_CLASS = "/** @constructor\n */\n" + "function Suggest() {}\n" + "Suggest.prototype.a = 1;\n" + "Suggest.prototype.veryPossible = 1;\n" + "Suggest.prototype.veryPossible2 = 1;\n"; @Override public void setUp() throws Exception { super.setUp(); reportMissingOverrides = CheckLevel.WARNING; } public void testInitialTypingScope() { Scope s = new TypedScopeCreator(compiler, CodingConventions.getDefault()).createInitialScope( new Node(Token.BLOCK)); assertTypeEquals(ARRAY_FUNCTION_TYPE, s.getVar("Array").getType()); assertTypeEquals(BOOLEAN_OBJECT_FUNCTION_TYPE, s.getVar("Boolean").getType()); assertTypeEquals(DATE_FUNCTION_TYPE, s.getVar("Date").getType()); assertTypeEquals(ERROR_FUNCTION_TYPE, s.getVar("Error").getType()); assertTypeEquals(EVAL_ERROR_FUNCTION_TYPE, s.getVar("EvalError").getType()); assertTypeEquals(NUMBER_OBJECT_FUNCTION_TYPE, s.getVar("Number").getType()); assertTypeEquals(OBJECT_FUNCTION_TYPE, s.getVar("Object").getType()); assertTypeEquals

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>(RANGE_ERROR_FUNCTION_TYPE, s.getVar("RangeError").getType()); assertTypeEquals(REFERENCE_ERROR_FUNCTION_TYPE, s.getVar("ReferenceError").getType()); assertTypeEquals(REGEXP_FUNCTION_TYPE, s.getVar("RegExp").getType()); assertTypeEquals(STRING_OBJECT_FUNCTION_TYPE, s.getVar("String").getType()); assertTypeEquals(SYNTAX_ERROR_FUNCTION_TYPE, s.getVar("SyntaxError").getType()); assertTypeEquals(TYPE_ERROR_FUNCTION_TYPE, s.getVar("TypeError").getType()); assertTypeEquals(URI_ERROR_FUNCTION_TYPE, s.getVar("URIError").getType()); } public void testPrivateType() throws Exception { testTypes( "/** @private {number} */ var x = false;", "initializing variable\n" + "found : boolean\n" + "required: number"); } public void testTypeCheck1() throws Exception { testTypes("/**@return {void}*/function foo(){ if (foo()) return; }"); } public void testTypeCheck2() throws Exception { testTypes("/**@return {void}*/function foo(){ var x=foo(); x--; }", "increment/decrement\n" + "found : undefined\n" + "required: number"); } public void testTypeCheck4() throws Exception { testTypes("/**@return {void}*/function foo(){ !foo(); }"); } public void testTypeCheck5() throws Exception { testTypes("/**@return {void}*/function foo(){ var a = +foo(); }", "sign operator\n" + "found : undefined\n" + "required: number"); } public void testTypeCheck6() throws Exception { testTypes( "/**@return {void}*/function foo(){" + "/** @type {undefined|number} */var a;if (a == foo())return;}"); } public void testTypeCheck8() throws Exception { testTypes("/**@return {void}*/function foo(){do {} while (foo());}"); } public void testTypeCheck9() throws Exception { testTypes("/**@return {void}*/function foo(){while (foo());}"); } public void testTypeCheck10() throws Exception { testTypes("/**@return {void}*/function foo(){for (;foo(););}"); } public void testTypeCheck11() throws Exception { testTypes("/**@type !Number */var a;" + "/**@type !String */var b;" + "a = b;", "assignment\n" + "found : String\n" + "required: Number"); } public void testTypeCheck12() throws Exception { testTypes("/**@return {!Object}*/function foo(){var a = 3^foo();}", "bad right operand to bitwise operator\n" + "found : Object\n" + "required: (boolean|null|number|string|undefined)"); } public void testTypeCheck13() throws Exception { testTypes("/**@type {!Number|!String}*/var i; i=/xx/;", "assignment\n

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> = function b(){};"); // a declared, b is not assertTrue(p.scope.isDeclared("a", false)); assertFalse(p.scope.isDeclared("b", false)); // checking that a has the correct assigned type assertEquals("function (): undefined", p.scope.getVar("a").getType().toString()); } public void testScoping11() throws Exception { // named function expressions create a binding in their body only // the return is wrong but the assignment is OK since the type of b is ? testTypes( "/** @return {number} */var a = function b(){ return b };", "inconsistent return type\n" + "found : function (): number\n" + "required: number"); } public void testScoping12() throws Exception { testTypes( "/** @constructor */ function F() {}" + "/** @type {number} */ F.prototype.bar = 3;" + "/** @param {!F} f */ function g(f) {" + " /** @return {string} */" + " function h() {" + " return f.bar;" + " }" + "}", "inconsistent return type\n" + "found : number\n" + "required: string"); } public void testFunctionArguments1() throws Exception { testFunctionType( "/** @param {number} a\n@return {string} */" + "function f(a) {}", "function (number): string"); } public void testFunctionArguments2() throws Exception { testFunctionType( "/** @param {number} opt_a\n@return {string} */" + "function f(opt_a) {}", "function (number=): string"); } public void testFunctionArguments3() throws Exception { testFunctionType( "/** @param {number} b\n@return {string} */" + "function f(a,b) {}", "function (?, number): string"); } public void testFunctionArguments4() throws Exception { testFunctionType( "/** @param {number} opt_a\n@return {string} */" + "function f(a,opt_a) {}", "function (?, number=): string"); } public void testFunctionArguments5() throws Exception { testTypes( "function a(opt_a,a) {}", "optional arguments must be at the end"); } public void testFunctionArguments6() throws Exception { testTypes( "function a(var_args,a) {}", "variable length argument must be last"); } public void testFunctionArguments7() throws Exception { testTypes( "/** @param {number} opt_a\n@return {string} */" + "function a(a,opt_a,var_args) {}"); } public void testFunctionArguments8() throws Exception { testTypes( "function a(a,opt_a,var_args,b) {}", "variable length argument must be last"); } public void testFunctionArguments9() throws Exception { // testing that only one error is reported testTypes

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> Object"); } public void testArrayAccess7() throws Exception { testTypes("var bar = void 0; bar[0];", "only arrays or objects can be accessed\n" + "found : undefined\n" + "required: Object"); } public void testArrayAccess8() throws Exception { // Verifies that we don't emit two warnings, because // the var has been dereferenced after the first one. testTypes("var bar = void 0; bar[0]; bar[1];", "only arrays or objects can be accessed\n" + "found : undefined\n" + "required: Object"); } public void testArrayAccess9() throws Exception { testTypes("/** @return {?Array} */ function f() { return []; }" + "f()[{}]", "array access\n" + "found : {}\n" + "required: number"); } public void testPropAccess() throws Exception { testTypes("/** @param {*} x */var f = function(x) {\n" + "var o = String(x);\n" + "if (typeof o['a'] != 'undefined') { return o['a']; }\n" + "return null;\n" + "};"); } public void testPropAccess2() throws Exception { testTypes("var bar = void 0; bar.baz;", "No properties on this expression\n" + "found : undefined\n" + "required: Object"); } public void testPropAccess3() throws Exception { // Verifies that we don't emit two warnings, because // the var has been dereferenced after the first one. testTypes("var bar = void 0; bar.baz; bar.bax;", "No properties on this expression\n" + "found : undefined\n" + "required: Object"); } public void testPropAccess4() throws Exception { testTypes("/** @param {*} x */ function f(x) { return x['hi']; }"); } public void testSwitchCase1() throws Exception { testTypes("/**@type number*/var a;" + "/**@type string*/var b;" + "switch(a){case b:;}", "case expression doesn't match switch\n" + "found : string\n" + "required: number"); } public void testSwitchCase2() throws Exception { testTypes("var a = null; switch (typeof a) { case 'foo': }"); } public void testVar1() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope("/** @type {(string,null)} */var a = null"); assertTypeEquals(createUnionType(STRING_TYPE, NULL_TYPE), p.scope.getVar("a").getType()); } public void testVar2() throws Exception { testTypes("/** @type {Function} */ var a = function(){}"); } public void testVar3() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope("var a = 3;"); assertTypeEquals(NUMBER_TYPE, p.scope.getVar("a").getType());

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> } public void testVar4() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope( "var a = 3; a = 'string';"); assertTypeEquals(createUnionType(STRING_TYPE, NUMBER_TYPE), p.scope.getVar("a").getType()); } public void testVar5() throws Exception { testTypes("var goog = {};" + "/** @type string */goog.foo = 'hello';" + "/** @type number */var a = goog.foo;", "initializing variable\n" + "found : string\n" + "required: number"); } public void testVar6() throws Exception { testTypes( "function f() {" + " return function() {" + " /** @type {!Date} */" + " var a = 7;" + " };" + "}", "initializing variable\n" + "found : number\n" + "required: Date"); } public void testVar7() throws Exception { testTypes("/** @type number */var a, b;", "declaration of multiple variables with shared type information"); } public void testVar8() throws Exception { testTypes("var a, b;"); } public void testVar9() throws Exception { testTypes("/** @enum */var a;", "enum initializer must be an object literal or an enum"); } public void testVar10() throws Exception { testTypes("/** @type !Number */var foo = 'abc';", "initializing variable\n" + "found : string\n" + "required: Number"); } public void testVar11() throws Exception { testTypes("var /** @type !Date */foo = 'abc';", "initializing variable\n" + "found : string\n" + "required: Date"); } public void testVar12() throws Exception { testTypes("var /** @type !Date */foo = 'abc', " + "/** @type !RegExp */bar = 5;", new String[] { "initializing variable\n" + "found : string\n" + "required: Date", "initializing variable\n" + "found : number\n" + "required: RegExp"}); } public void testVar13() throws Exception { // this caused an NPE testTypes("var /** @type number */a,a;"); } public void testVar14() throws Exception { testTypes("/** @return {number} */ function f() { var x; return x; }", "inconsistent return type\n" + "found : undefined\n" + "required: number"); } public void testVar15() throws Exception { testTypes("/** @return {number} */" + "function f() { var x = x || {}; return x; }", "inconsistent return type\n" + "found : {}\n" + "required: number"); } public void testAssign1() throws Exception { testTypes("var goog = {};" + "/** @

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>() throws Exception { testTypes("/** @constructor */function A(){}" + "/** @constructor\n * @extends A */function B(){}" + "/** @param {B} b" + "\n @return {(A,undefined)} */function foo(b){return b}"); } /** * Tests that assigning two untyped functions to a variable whose type is * inferred and calling this variable is legal. */ public void testBug911118() throws Exception { // verifying the type assigned to function expressions assigned variables Scope s = parseAndTypeCheckWithScope("var a = function(){};").scope; JSType type = s.getVar("a").getType(); assertEquals("function (): undefined", type.toString()); // verifying the bug example testTypes("function nullFunction() {};" + "var foo = nullFunction;" + "foo = function() {};" + "foo();"); } public void testBug909000() throws Exception { testTypes("/** @constructor */function A(){}\n" + "/** @param {!A} a\n" + "@return {boolean}*/\n" + "function y(a) { return a }", "inconsistent return type\n" + "found : A\n" + "required: boolean"); } public void testBug930117() throws Exception { testTypes( "/** @param {boolean} x */function f(x){}" + "f(null);", "actual parameter 1 of f does not match formal parameter\n" + "found : null\n" + "required: boolean"); } public void testBug1484445() throws Exception { testTypes( "/** @constructor */ function Foo() {}" + "/** @type {number?} */ Foo.prototype.bar = null;" + "/** @type {number?} */ Foo.prototype.baz = null;" + "/** @param {Foo} foo */" + "function f(foo) {" + " while (true) {" + " if (foo.bar == null && foo.baz == null) {" + " foo.bar;" + " }" + " }" + "}"); } public void testBug1859535() throws Exception { testTypes( "/**\n" + " * @param {Function} childCtor Child class.\n" + " * @param {Function} parentCtor Parent class.\n" + " */" + "var inherits = function(childCtor, parentCtor) {" + " /** @constructor */" + " function tempCtor() {};" + " tempCtor.prototype = parentCtor.prototype;" + " childCtor.superClass_ = parentCtor.prototype;" + " childCtor.prototype = new tempCtor();" + " /** @override */ childCtor.prototype.constructor = childCtor;" + "};" + "/**" + " * @param {Function} constructor\n" + " * @param {Object} var_args\n" + " * @return {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>" + "var a = new A();"); JSType aType = p.scope.getVar("a").getType(); assertTrue(aType instanceof ObjectType); ObjectType aObjectType = (ObjectType) aType; assertEquals("A", aObjectType.getConstructor().getReferenceName()); } public void testNew7() throws Exception { testTypes("/** @param {Function} opt_constructor */" + "function foo(opt_constructor) {" + "if (opt_constructor) { new opt_constructor; }" + "}"); } public void testNew8() throws Exception { testTypes("/** @param {Function} opt_constructor */" + "function foo(opt_constructor) {" + "new opt_constructor;" + "}"); } public void testNew9() throws Exception { testTypes("/** @param {Function} opt_constructor */" + "function foo(opt_constructor) {" + "new (opt_constructor || Array);" + "}"); } public void testNew10() throws Exception { testTypes("var goog = {};" + "/** @param {Function} opt_constructor */" + "goog.Foo = function (opt_constructor) {" + "new (opt_constructor || Array);" + "}"); } public void testNew11() throws Exception { testTypes("/** @param {Function} c1 */" + "function f(c1) {" + " var c2 = function(){};" + " c1.prototype = new c2;" + "}", TypeCheck.NOT_A_CONSTRUCTOR); } public void testNew12() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope("var a = new Array();"); Var a = p.scope.getVar("a"); assertTypeEquals(ARRAY_TYPE, a.getType()); } public void testNew13() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope( "/** @constructor */function FooBar(){};" + "var a = new FooBar();"); Var a = p.scope.getVar("a"); assertTrue(a.getType() instanceof ObjectType); assertEquals("FooBar", a.getType().toString()); } public void testNew14() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope( "/** @constructor */var FooBar = function(){};" + "var a = new FooBar();"); Var a = p.scope.getVar("a"); assertTrue(a.getType() instanceof ObjectType); assertEquals("FooBar", a.getType().toString()); } public void testNew15() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope( "var goog = {};" + "/** @constructor */goog.A = function(){};" + "var a = new goog.A();"); Var a = p.scope.getVar("a"); assertTrue(a.getType() instanceof ObjectType); assertEquals("goog.A", a.getType().toString()); } public void testNew16() throws Exception { testTypes( "/** \n" + " * @param {string} x \n" + " * @constructor \n"

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> == 'boolean' ||" + "typeof 123 == 'undefined' ||" + "typeof 123 == 'function' ||" + "typeof 123 == 'object' ||" + "typeof 123 == 'unknown'); }"); } public void testConstructorType1() throws Exception { testTypes("/**@constructor*/function Foo(){}" + "/**@type{!Foo}*/var f = new Date();", "initializing variable\n" + "found : Date\n" + "required: Foo"); } public void testConstructorType2() throws Exception { testTypes("/**@constructor*/function Foo(){\n" + "/**@type{Number}*/this.bar = new Number(5);\n" + "}\n" + "/**@type{Foo}*/var f = new Foo();\n" + "/**@type{Number}*/var n = f.bar;"); } public void testConstructorType3() throws Exception { // Reverse the declaration order so that we know that Foo is getting set // even on an out-of-order declaration sequence. testTypes("/**@type{Foo}*/var f = new Foo();\n" + "/**@type{Number}*/var n = f.bar;" + "/**@constructor*/function Foo(){\n" + "/**@type{Number}*/this.bar = new Number(5);\n" + "}\n"); } public void testConstructorType4() throws Exception { testTypes("/**@constructor*/function Foo(){\n" + "/**@type{!Number}*/this.bar = new Number(5);\n" + "}\n" + "/**@type{!Foo}*/var f = new Foo();\n" + "/**@type{!String}*/var n = f.bar;", "initializing variable\n" + "found : Number\n" + "required: String"); } public void testConstructorType5() throws Exception { testTypes("/**@constructor*/function Foo(){}\n" + "if (Foo){}\n"); } public void testConstructorType6() throws Exception { testTypes("/** @constructor */\n" + "function bar() {}\n" + "function _foo() {\n" + " /** @param {bar} x */\n" + " function f(x) {}\n" + "}"); } public void testConstructorType7() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope("/** @constructor */function A(){};"); JSType type = p.scope.getVar("A").getType(); assertTrue(type instanceof FunctionType); FunctionType fType = (FunctionType) type; assertEquals("A", fType.getReferenceName()); } public void testConstructorType8() throws Exception { testTypes( "var ns = {};" + "ns.create = function() { return function() {}; };" + "/** @constructor */ ns.Foo = ns.create();" + "ns.Foo.prototype = {x: 0, y: 0};" + "/**\n" +

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> {};\n" + "/** @type {number} */ n.x = 1;\n" + "/** @return {boolean} */function f() { return n.x; }", "inconsistent return type\n" + "found : number\n" + "required: boolean"); } public void testDefinePropertyOnNullableObject2() throws Exception { testTypes("/** @constructor */ var T = function() {};\n" + "/** @param {T} t\n@return {boolean} */function f(t) {\n" + "t.x = 1; return t.x; }", "inconsistent return type\n" + "found : number\n" + "required: boolean"); } public void testUnknownConstructorInstanceType1() throws Exception { testTypes("/** @return {Array} */ function g(f) { return new f(); }"); } public void testUnknownConstructorInstanceType2() throws Exception { testTypes("function g(f) { return /** @type Array */(new f()); }"); } public void testUnknownConstructorInstanceType3() throws Exception { testTypes("function g(f) { var x = new f(); x.a = 1; return x; }"); } public void testUnknownPrototypeChain() throws Exception { testTypes("/**\n" + "* @param {Object} co\n" + " * @return {Object}\n" + " */\n" + "function inst(co) {\n" + " /** @constructor */\n" + " var c = function() {};\n" + " c.prototype = co.prototype;\n" + " return new c;\n" + "}"); } public void testNamespacedConstructor() throws Exception { Node root = parseAndTypeCheck( "var goog = {};" + "/** @constructor */ goog.MyClass = function() {};" + "/** @return {!goog.MyClass} */ " + "function foo() { return new goog.MyClass(); }"); JSType typeOfFoo = root.getLastChild().getJSType(); assert(typeOfFoo instanceof FunctionType); JSType retType = ((FunctionType) typeOfFoo).getReturnType(); assert(retType instanceof ObjectType); assertEquals("goog.MyClass", ((ObjectType) retType).getReferenceName()); } public void testComplexNamespace() throws Exception { String js = "var goog = {};" + "goog.foo = {};" + "goog.foo.bar = 5;"; TypeCheckResult p = parseAndTypeCheckWithScope(js); // goog type in the scope JSType googScopeType = p.scope.getVar("goog").getType(); assertTrue(googScopeType instanceof ObjectType); assertTrue("foo property not present on goog type", ((ObjectType) googScopeType).hasProperty("foo")); assertFalse("bar property present on goog type", ((ObjectType) googScopeType).hasProperty("bar")); // goog type on the VAR node Node varNode = p.root.getFirstChild(); assertEquals(Token.VAR, varNode.getType()); JSType googNodeType = varNode.getFirstChild().

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>getJSType(); assertTrue(googNodeType instanceof ObjectType); // goog scope type and goog type on VAR node must be the same assertTrue(googScopeType == googNodeType); // goog type on the left of the GETPROP node (under fist ASSIGN) Node getpropFoo1 = varNode.getNext().getFirstChild().getFirstChild(); assertEquals(Token.GETPROP, getpropFoo1.getType()); assertEquals("goog", getpropFoo1.getFirstChild().getString()); JSType googGetpropFoo1Type = getpropFoo1.getFirstChild().getJSType(); assertTrue(googGetpropFoo1Type instanceof ObjectType); // still the same type as the one on the variable assertTrue(googGetpropFoo1Type == googScopeType); // the foo property should be defined on goog JSType googFooType = ((ObjectType) googScopeType).getPropertyType("foo"); assertTrue(googFooType instanceof ObjectType); // goog type on the left of the GETPROP lower level node // (under second ASSIGN) Node getpropFoo2 = varNode.getNext().getNext() .getFirstChild().getFirstChild().getFirstChild(); assertEquals(Token.GETPROP, getpropFoo2.getType()); assertEquals("goog", getpropFoo2.getFirstChild().getString()); JSType googGetpropFoo2Type = getpropFoo2.getFirstChild().getJSType(); assertTrue(googGetpropFoo2Type instanceof ObjectType); // still the same type as the one on the variable assertTrue(googGetpropFoo2Type == googScopeType); // goog.foo type on the left of the top-level GETPROP node // (under second ASSIGN) JSType googFooGetprop2Type = getpropFoo2.getJSType(); assertTrue("goog.foo incorrectly annotated in goog.foo.bar selection", googFooGetprop2Type instanceof ObjectType); ObjectType googFooGetprop2ObjectType = (ObjectType) googFooGetprop2Type; assertFalse("foo property present on goog.foo type", googFooGetprop2ObjectType.hasProperty("foo")); assertTrue("bar property not present on goog.foo type", googFooGetprop2ObjectType.hasProperty("bar")); assertTypeEquals("bar property on goog.foo type incorrectly inferred", NUMBER_TYPE, googFooGetprop2ObjectType.getPropertyType("bar")); } public void testAddingMethodsUsingPrototypeIdiomSimpleNamespace() throws Exception { Node js1Node = parseAndTypeCheck( "/** @constructor */function A() {}" + "A.prototype.m1 = 5"); ObjectType instanceType = getInstanceType(js1Node); assertEquals(NATIVE_PROPERTIES_COUNT + 1, instanceType.getPropertiesCount()); checkObjectType(instanceType, "m1", NUMBER_TYPE); } public void testAddingMethodsUsingPrototypeIdiomComplexNamespace1() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope( "var goog = {};" + "goog.A = /** @constructor */function() {};" + "/** @type number */goog.A.prototype.m1 = 5"); testAddingMethodsUsingPrototypeIdiomComplexNamespace(p); } public void testAddingMethodsUsingPrototypeIdiomComplexNamespace2() throws Exception { TypeCheckResult

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> p = parseAndTypeCheckWithScope( "var goog = {};" + "/** @constructor */goog.A = function() {};" + "/** @type number */goog.A.prototype.m1 = 5"); testAddingMethodsUsingPrototypeIdiomComplexNamespace(p); } private void testAddingMethodsUsingPrototypeIdiomComplexNamespace( TypeCheckResult p) { ObjectType goog = (ObjectType) p.scope.getVar("goog").getType(); assertEquals(NATIVE_PROPERTIES_COUNT + 1, goog.getPropertiesCount()); JSType googA = goog.getPropertyType("A"); assertNotNull(googA); assertTrue(googA instanceof FunctionType); FunctionType googAFunction = (FunctionType) googA; ObjectType classA = googAFunction.getInstanceType(); assertEquals(NATIVE_PROPERTIES_COUNT + 1, classA.getPropertiesCount()); checkObjectType(classA, "m1", NUMBER_TYPE); } public void testAddingMethodsPrototypeIdiomAndObjectLiteralSimpleNamespace() throws Exception { Node js1Node = parseAndTypeCheck( "/** @constructor */function A() {}" + "A.prototype = {m1: 5, m2: true}"); ObjectType instanceType = getInstanceType(js1Node); assertEquals(NATIVE_PROPERTIES_COUNT + 2, instanceType.getPropertiesCount()); checkObjectType(instanceType, "m1", NUMBER_TYPE); checkObjectType(instanceType, "m2", BOOLEAN_TYPE); } public void testDontAddMethodsIfNoConstructor() throws Exception { Node js1Node = parseAndTypeCheck( "function A() {}" + "A.prototype = {m1: 5, m2: true}"); JSType functionAType = js1Node.getFirstChild().getJSType(); assertEquals("function (): undefined", functionAType.toString()); assertTypeEquals(UNKNOWN_TYPE, U2U_FUNCTION_TYPE.getPropertyType("m1")); assertTypeEquals(UNKNOWN_TYPE, U2U_FUNCTION_TYPE.getPropertyType("m2")); } public void testFunctionAssignement() throws Exception { testTypes("/**" + "* @param {string} ph0" + "* @param {string} ph1" + "* @return {string}" + "*/" + "function MSG_CALENDAR_ACCESS_ERROR(ph0, ph1) {return ''}" + "/** @type {Function} */" + "var MSG_CALENDAR_ADD_ERROR = MSG_CALENDAR_ACCESS_ERROR;"); } public void testAddMethodsPrototypeTwoWays() throws Exception { Node js1Node = parseAndTypeCheck( "/** @constructor */function A() {}" + "A.prototype = {m1: 5, m2: true};" + "A.prototype.m3 = 'third property!';"); ObjectType instanceType = getInstanceType(js1Node); assertEquals("A", instanceType.toString()); assertEquals(NATIVE_PROPERTIES_COUNT + 3, instanceType.getPropertiesCount()); checkObjectType(instanceType, "m1", NUMBER_TYPE); checkObjectType(instanceType, "m2", BOOLEAN_TYPE); checkObjectType(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { return 3 };", "inconsistent return type\n" + "found : number\n" + "required: string"); } public void testInterfacePropertyNotImplemented() throws Exception { testTypes( "/** @interface */function Int() {};" + "/** @desc description */Int.prototype.foo = function() {};" + "/** @constructor\n @implements {Int} */function Foo() {};", "property foo on interface Int is not implemented by type Foo"); } public void testInterfacePropertyNotImplemented2() throws Exception { testTypes( "/** @interface */function Int() {};" + "/** @desc description */Int.prototype.foo = function() {};" + "/** @interface \n @extends {Int} */function Int2() {};" + "/** @constructor\n @implements {Int2} */function Foo() {};", "property foo on interface Int is not implemented by type Foo"); } /** * Verify that templatized interfaces enforce their template type values. */ public void testInterfacePropertyNotImplemented3() throws Exception { testTypes( "/** @interface\n @template T */function Int() {};" + "/** @desc description\n @return {T} */Int.prototype.foo = function() {};" + "/** @constructor\n @implements {Int.<string>} */function Foo() {};" + "/** @return {number}\n @override */Foo.prototype.foo = function() {};", "mismatch of the foo property type and the type of the property it " + "overrides from interface Int\n" + "original: function (this:Int): string\n" + "override: function (this:Foo): number"); } public void testStubConstructorImplementingInterface() throws Exception { // This does not throw a warning for unimplemented property because Foo is // just a stub. testTypes( // externs "/** @interface */ function Int() {}\n" + "/** @desc description */Int.prototype.foo = function() {};" + "/** @constructor \n @implements {Int} */ var Foo;\n", "", null, false); } public void testObjectLiteral() throws Exception { Node n = parseAndTypeCheck("var a = {m1: 7, m2: 'hello'}"); Node nameNode = n.getFirstChild().getFirstChild(); Node objectNode = nameNode.getFirstChild(); // node extraction assertEquals(Token.NAME, nameNode.getType()); assertEquals(Token.OBJECTLIT, objectNode.getType()); // value's type ObjectType objectType = (ObjectType) objectNode.getJSType(); assertTypeEquals(NUMBER_TYPE, objectType.getPropertyType("m1")); assertTypeEquals(STRING_TYPE, objectType.getPropertyType("m2")); // variable's type assertTypeEquals(objectType, nameNode.getJSType()); } public void testObjectLiteralDeclaration1() throws Exception { testTypes( "var x = {" + "/** @type {boolean} */ abc: true," + "/** @type {number} */ 'def': 0," + "/** @type {string} */ 3: 'fgh

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> = {A: 1,B: 2,C: 3};"; assertEquals(100.0, getTypedPercent(js), 0.1); } public void testGetTypedPercent6() throws Exception { String js = "a = {TRUE: 1, FALSE: 0};"; assertEquals(100.0, getTypedPercent(js), 0.1); } private double getTypedPercent(String js) throws Exception { Node n = compiler.parseTestCode(js); Node externs = new Node(Token.BLOCK); Node externAndJsRoot = new Node(Token.BLOCK, externs, n); externAndJsRoot.setIsSyntheticBlock(true); TypeCheck t = makeTypeCheck(); t.processForTesting(null, n); return t.getTypedPercent(); } private static ObjectType getInstanceType(Node js1Node) { JSType type = js1Node.getFirstChild().getJSType(); assertNotNull(type); assertTrue(type instanceof FunctionType); FunctionType functionType = (FunctionType) type; assertTrue(functionType.isConstructor()); return functionType.getInstanceType(); } public void testPrototypePropertyReference() throws Exception { TypeCheckResult p = parseAndTypeCheckWithScope("" + "/** @constructor */\n" + "function Foo() {}\n" + "/** @param {number} a */\n" + "Foo.prototype.bar = function(a){};\n" + "/** @param {Foo} f */\n" + "function baz(f) {\n" + " Foo.prototype.bar.call(f, 3);\n" + "}"); assertEquals(0, compiler.getErrorCount()); assertEquals(0, compiler.getWarningCount()); assertTrue(p.scope.getVar("Foo").getType() instanceof FunctionType); FunctionType fooType = (FunctionType) p.scope.getVar("Foo").getType(); assertEquals("function (this:Foo, number): undefined", fooType.getPrototype().getPropertyType("bar").toString()); } public void testResolvingNamedTypes() throws Exception { String js = "" + "/** @constructor */\n" + "var Foo = function() {}\n" + "/** @param {number} a */\n" + "Foo.prototype.foo = function(a) {\n" + " return this.baz().toString();\n" + "};\n" + "/** @return {Baz} */\n" + "Foo.prototype.baz = function() { return new Baz(); };\n" + "/** @constructor\n" + " * @extends Foo */\n" + "var Bar = function() {};" + "/** @constructor */\n" + "var Baz = function() {};"; assertEquals(100.0, getTypedPercent(js), 0.1); } public void testMissingProperty1() throws Exception { testTypes( "/** @constructor */ function Foo() {}" + "Foo.prototype.bar = function() { return this.a; };" + "Foo.prototype.baz = function

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> Node n = parseAndTypeCheck("var undefined;"); assertTypeEquals(registry.getNativeType(JSTypeNative.VOID_TYPE), n.getFirstChild().getFirstChild().getJSType()); } public void testFlowScopeBug1() throws Exception { Node n = parseAndTypeCheck("/** @param {number} a \n" + "* @param {number} b */\n" + "function f(a, b) {\n" + "/** @type number */" + "var i = 0;" + "for (; (i + a) < b; ++i) {}}"); // check the type of the add node for i + f assertTypeEquals(registry.getNativeType(JSTypeNative.NUMBER_TYPE), n.getFirstChild().getLastChild().getLastChild().getFirstChild() .getNext().getFirstChild().getJSType()); } public void testFlowScopeBug2() throws Exception { Node n = parseAndTypeCheck("/** @constructor */ function Foo() {};\n" + "Foo.prototype.hi = false;" + "function foo(a, b) {\n" + " /** @type Array */" + " var arr;" + " /** @type number */" + " var iter;" + " for (iter = 0; iter < arr.length; ++ iter) {" + " /** @type Foo */" + " var afoo = arr[iter];" + " afoo;" + " }" + "}"); // check the type of afoo when referenced assertTypeEquals(registry.createNullableType(registry.getType("Foo")), n.getLastChild().getLastChild().getLastChild().getLastChild() .getLastChild().getLastChild().getJSType()); } public void testAddSingletonGetter() { Node n = parseAndTypeCheck( "/** @constructor */ function Foo() {};\n" + "goog.addSingletonGetter(Foo);"); ObjectType o = (ObjectType) n.getFirstChild().getJSType(); assertEquals("function (): Foo", o.getPropertyType("getInstance").toString()); assertEquals("Foo", o.getPropertyType("instance_").toString()); } public void testTypeCheckStandaloneAST() throws Exception { Node n = compiler.parseTestCode("function Foo() { }"); typeCheck(n); MemoizedScopeCreator scopeCreator = new MemoizedScopeCreator( new TypedScopeCreator(compiler)); Scope topScope = scopeCreator.createScope(n, null); Node second = compiler.parseTestCode("new Foo"); Node externs = new Node(Token.BLOCK); Node externAndJsRoot = new Node(Token.BLOCK, externs, second); externAndJsRoot.setIsSyntheticBlock(true); new TypeCheck( compiler, new SemanticReverseAbstractInterpreter( compiler.getCodingConvention(), registry), registry, topScope, scopeCreator, CheckLevel.WARNING) .process(null, second); assertEquals(1, compiler.getWarningCount()); assertEquals("cannot instantiate non-constructor", compiler.getWarnings()[0].description); } public void testUpdateParameterTypeOnClosure() throws Exception { testTypes(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>MissingOverrides); } void testTypes(String js, String[] warnings) throws Exception { Node n = compiler.parseTestCode(js); assertEquals(0, compiler.getErrorCount()); Node externsNode = new Node(Token.BLOCK); // create a parent node for the extern and source blocks new Node(Token.BLOCK, externsNode, n); makeTypeCheck().processForTesting(null, n); assertEquals(0, compiler.getErrorCount()); if (warnings != null) { assertEquals(warnings.length, compiler.getWarningCount()); JSError[] messages = compiler.getWarnings(); for (int i = 0; i < warnings.length && i < compiler.getWarningCount(); i++) { assertEquals(warnings[i], messages[i].description); } } else { assertEquals(0, compiler.getWarningCount()); } } String suppressMissingProperty(String ... props) { String result = "function dummy(x) { "; for (String prop : props) { result += "x." + prop + " = 3;"; } return result + "}"; } private static class TypeCheckResult { private final Node root; private final Scope scope; private TypeCheckResult(Node root, Scope scope) { this.root = root; this.scope = scope; } } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> candidate == entry); prioritizeFromEntryNode(candidate); } } } // At this point, all reachable nodes have been given a priority, but // unreachable nodes have not been given a priority. Put them last. // Presumably, it doesn't really matter what priority they get, since // this shouldn't happen in real code. for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) { if (!nodePriorities.containsKey(candidate)) { nodePriorities.put(candidate, ++priorityCounter); } } // Again, the implicit return node is always last. nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter); } /** * Given an entry node, find all the nodes reachable from that node * and prioritize them. */ private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) { PriorityQueue<DiGraphNode<Node, Branch>> worklist = new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator); worklist.add(entry); while (!worklist.isEmpty()) { DiGraphNode<Node, Branch> current = worklist.remove(); if (nodePriorities.containsKey(current)) { continue; } nodePriorities.put(current, ++priorityCounter); List<DiGraphNode<Node, Branch>> successors = cfg.getDirectedSuccNodes(current); for (DiGraphNode<Node, Branch> candidate : successors) { worklist.add(candidate); } } } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { astPosition.put(n, astPositionCounter++); switch (n.getType()) { case Token.FUNCTION: if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) { exceptionHandler.push(n); return true; } return false; case Token.TRY: exceptionHandler.push(n); return true; } /* * We are going to stop the traversal depending on what the node's parent * is. * * We are only interested in adding edges between nodes that change control * flow. The most obvious ones are loops and IF-ELSE's. A statement * transfers control to its next sibling. * * In case of an expression tree, there is no control flow within the tree * even when there are short circuited operators and conditionals. When we * are doing data flow analysis, we will simply synthesize lattices up the * expression tree by finding the meet at each expression node. * * For example: within a Token.SWITCH, the expression in question does not * change the control flow and need not to be considered. */ if (parent != null) { switch (parent.getType()) { case Token.FOR: // Only traverse the body of the for loop. return n == parent.getLastChild(); // Skip the conditions. case Token.IF: case Token.WHILE: case Token.WITH: return n != parent.getFirstChild(); case Token.DO: return n != parent.getFirstChild().getNext(); //

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> Only traverse the body of the cases case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.LABEL: return n != parent.getFirstChild(); case Token.FUNCTION: return n == parent.getFirstChild().getNext().getNext(); case Token.CONTINUE: case Token.BREAK: case Token.EXPR_RESULT: case Token.VAR: case Token.RETURN: case Token.THROW: return false; case Token.TRY: /* Just before we are about to visit the second child of the TRY node, * we know that we will be visiting either the CATCH or the FINALLY. * In other words, we know that the post order traversal of the TRY * block has been finished, no more exceptions can be caught by the * handler at this TRY block and should be taken out of the stack. */ if (n == parent.getFirstChild().getNext()) { Preconditions.checkState(exceptionHandler.peek() == parent); exceptionHandler.pop(); } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.IF: handleIf(n); return; case Token.WHILE: handleWhile(n); return; case Token.DO: handleDo(n); return; case Token.FOR: handleFor(n); return; case Token.SWITCH: handleSwitch(n); return; case Token.CASE: handleCase(n); return; case Token.DEFAULT_CASE: handleDefault(n); return; case Token.BLOCK: case Token.SCRIPT: handleStmtList(n); return; case Token.FUNCTION: handleFunction(n); return; case Token.EXPR_RESULT: handleExpr(n); return; case Token.THROW: handleThrow(n); return; case Token.TRY: handleTry(n); return; case Token.CATCH: handleCatch(n); return; case Token.BREAK: handleBreak(n); return; case Token.CONTINUE: handleContinue(n); return; case Token.RETURN: handleReturn(n); return; case Token.WITH: handleWith(n); return; case Token.LABEL: return; default: handleStmt(n); return; } } private void handleIf(Node node) { Node thenBlock = node.getFirstChild().getNext(); Node elseBlock = thenBlock.getNext(); createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock)); if (elseBlock == null) { createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); // not taken branch } else { createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock)); } connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleWhile(Node node) { // Control goes to the first statement if the condition evaluates to true. createEdge(node, Branch.ON_

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>UNCOND, node.getFirstChild().getNext()); } else { // No CASE, no DEFAULT createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleCase(Node node) { // Case is a bit tricky....First it goes into the body if condition is true. createEdge(node, Branch.ON_TRUE, node.getFirstChild().getNext()); // Look for the next CASE, skipping over DEFAULT. Node next = getNextSiblingOfType(node.getNext(), Token.CASE); if (next != null) { // Found a CASE Preconditions.checkState(next.isCase()); createEdge(node, Branch.ON_FALSE, next); } else { // No more CASE found, go back and search for a DEFAULT. Node parent = node.getParent(); Node deflt = getNextSiblingOfType( parent.getFirstChild().getNext(), Token.DEFAULT_CASE); if (deflt != null) { // Has a DEFAULT createEdge(node, Branch.ON_FALSE, deflt); } else { // No DEFAULT found, go to the follow of the SWITCH. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleDefault(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleWith(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getLastChild()); connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleStmtList(Node node) { Node parent = node.getParent(); // Special case, don't add a block of empty CATCH block to the graph. if (node.isBlock() && parent != null && parent.isTry() && NodeUtil.getCatchBlock(parent) == node && !NodeUtil.hasCatchHandler(node)) { return; } // A block transfer control to its first child if it is not empty. Node child = node.getFirstChild(); // Function declarations are skipped since control doesn't go into that // function (unless it is called) while (child != null && child.isFunction()) { child = child.getNext(); } if (child != null) { createEdge(node, Branch.UNCOND, computeFallThrough(child)); } else { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } // Synthetic blocks if (parent != null) { switch (parent.getType()) { case Token.DEFAULT_CASE: case Token.CASE: case Token.TRY: break; default: if (node.isBlock() && node.isSyntheticBlock()) { createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this)); } break; } } } private void handleFunction(Node node) { //

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> return statement, we should also transfer control * back to the caller of the function. * * 4. If the node is root then we have reached the end of what we have been * asked to traverse. * * In all cases we should transfer control to a "symbolic return" node. * This will make life easier for DFAs. */ Node parent = node.getParent(); if (parent == null || parent.isFunction() || (cfa != null && node == cfa.root)) { return null; } // If we are just before a IF/WHILE/DO/FOR: switch (parent.getType()) { // The follow() of any of the path from IF would be what follows IF. case Token.IF: return computeFollowNode(fromNode, parent, cfa); case Token.CASE: case Token.DEFAULT_CASE: // After the body of a CASE, the control goes to the body of the next // case, without having to go to the case condition. if (parent.getNext() != null) { if (parent.getNext().isCase()) { return parent.getNext().getFirstChild().getNext(); } else if (parent.getNext().isDefaultCase()) { return parent.getNext().getFirstChild(); } else { Preconditions.checkState(false, "Not reachable"); } } else { return computeFollowNode(fromNode, parent, cfa); } break; case Token.FOR: if (NodeUtil.isForIn(parent)) { return parent; } else { return parent.getFirstChild().getNext().getNext(); } case Token.WHILE: case Token.DO: return parent; case Token.TRY: // If we are coming out of the TRY block... if (parent.getFirstChild() == node) { if (NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(parent.getLastChild()); } else { // and have no FINALLY. return computeFollowNode(fromNode, parent, cfa); } // CATCH block. } else if (NodeUtil.getCatchBlock(parent) == node){ if (NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(node.getNext()); } else { return computeFollowNode(fromNode, parent, cfa); } // If we are coming out of the FINALLY block... } else if (parent.getLastChild() == node){ if (cfa != null) { for (Node finallyNode : cfa.finallyMap.get(parent)) { cfa.createEdge(fromNode, Branch.ON_EX, finallyNode); } } return computeFollowNode(fromNode, parent, cfa); } } // Now that we are done with the special cases follow should be its // immediate sibling, unless its sibling is a function Node nextSibling = node.getNext(); // Skip function declarations because control doesn't get pass into it. while (nextSibling != null && nextSibling.isFunction()) { nextSibling = nextSibling.getNext(); } if (nextSibling != null) { return computeFallThrough(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>nextSibling); } else { // If there are no more siblings, control is transferred up the AST. return computeFollowNode(fromNode, parent, cfa); } } /** * Computes the destination node of n when we want to fallthrough into the * subtree of n. We don't always create a CFG edge into n itself because of * DOs and FORs. */ static Node computeFallThrough(Node n) { switch (n.getType()) { case Token.DO: return computeFallThrough(n.getFirstChild()); case Token.FOR: if (NodeUtil.isForIn(n)) { return n.getFirstChild().getNext(); } return computeFallThrough(n.getFirstChild()); case Token.LABEL: return computeFallThrough(n.getLastChild()); default: return n; } } /** * Connects the two nodes in the control flow graph. * * @param fromNode Source. * @param toNode Destination. */ private void createEdge(Node fromNode, ControlFlowGraph.Branch branch, Node toNode) { cfg.createNode(fromNode); cfg.createNode(toNode); cfg.connectIfNotFound(fromNode, branch, toNode); } /** * Connects cfgNode to the proper CATCH block if target subtree might throw * an exception. If there are FINALLY blocks reached before a CATCH, it will * make the corresponding entry in finallyMap. */ private void connectToPossibleExceptionHandler(Node cfgNode, Node target) { if (mayThrowException(target) && !exceptionHandler.isEmpty()) { Node lastJump = cfgNode; for (Node handler : exceptionHandler) { if (handler.isFunction()) { return; } Preconditions.checkState(handler.isTry()); Node catchBlock = NodeUtil.getCatchBlock(handler); if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, handler.getLastChild()); } else { finallyMap.put(lastJump, handler.getLastChild()); } } else { // Has a catch. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, catchBlock); return; } else { finallyMap.put(lastJump, catchBlock); } } lastJump = handler; } } } /** * Get the next sibling (including itself) of one of the given types. */ private static Node getNextSiblingOfType(Node first, int ... types) { for (Node c = first; c != null; c = c.getNext()) { for (int type : types) { if (c.getType() == type) { return c; } } } return null; } /** * Checks if target is actually the break target of labeled continue. The * label can be null if it is an unlabeled break. */ public static boolean isBreakTarget(Node target, String label) { return isBreakStructure(target, label != null) && matchLabel(target.getParent(),

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> label); } /** * Checks if target is actually the continue target of labeled continue. The * label can be null if it is an unlabeled continue. */ private static boolean isContinueTarget( Node target, Node parent, String label) { return isContinueStructure(target) && matchLabel(parent, label); } /** * Check if label is actually referencing the target control structure. If * label is null, it always returns true. */ private static boolean matchLabel(Node target, String label) { if (label == null) { return true; } while (target.isLabel()) { if (target.getFirstChild().getString().equals(label)) { return true; } target = target.getParent(); } return false; } /** * Determines if the subtree might throw an exception. */ public static boolean mayThrowException(Node n) { switch (n.getType()) { case Token.CALL: case Token.GETPROP: case Token.GETELEM: case Token.THROW: case Token.NEW: case Token.ASSIGN: case Token.INC: case Token.DEC: case Token.INSTANCEOF: return true; case Token.FUNCTION: return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) { return true; } } return false; } /** * Determines whether the given node can be terminated with a BREAK node. */ static boolean isBreakStructure(Node n, boolean labeled) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.SWITCH: return true; case Token.BLOCK: case Token.IF: case Token.TRY: return labeled; default: return false; } } /** * Determines whether the given node can be advanced with a CONTINUE node. */ static boolean isContinueStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * Get the TRY block with a CATCH that would be run if n throws an exception. * @return The CATCH node or null if it there isn't a CATCH before the * the function terminates. */ static Node getExceptionHandler(Node n) { for (Node cur = n; !cur.isScript() && !cur.isFunction(); cur = cur.getParent()) { Node catchNode = getCatchHandlerForBlock(cur); if (catchNode != null) { return catchNode; } } return null; } /** * Locate the catch BLOCK given the first block in a TRY. * @return The CATCH node or null there is no catch handler. */ static Node getCatchHandlerForBlock(Node block) { if (block.isBlock() && block.getParent().isTry() && block.getParent().getFirstChild() == block)

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>, true); ObjectType GLOBAL_THIS = GLOBAL_THIS_CTOR.getInstanceType(); registerNativeType(JSTypeNative.GLOBAL_THIS, GLOBAL_THIS); // greatest function type, i.e. (NoType...) -> All FunctionType GREATEST_FUNCTION_TYPE = createNativeFunctionTypeWithVarArgs(ALL_TYPE, NO_TYPE); registerNativeType(JSTypeNative.GREATEST_FUNCTION_TYPE, GREATEST_FUNCTION_TYPE); // Register the prototype property. See the comments below in // registerPropertyOnType about the bootstrapping process. registerPropertyOnType("prototype", OBJECT_FUNCTION_TYPE); } private void initializeRegistry() { register(getNativeType(JSTypeNative.ARRAY_TYPE)); register(getNativeType(JSTypeNative.BOOLEAN_OBJECT_TYPE)); register(getNativeType(JSTypeNative.BOOLEAN_TYPE)); register(getNativeType(JSTypeNative.DATE_TYPE)); register(getNativeType(JSTypeNative.NULL_TYPE)); register(getNativeType(JSTypeNative.NULL_TYPE), "Null"); register(getNativeType(JSTypeNative.NUMBER_OBJECT_TYPE)); register(getNativeType(JSTypeNative.NUMBER_TYPE)); register(getNativeType(JSTypeNative.OBJECT_TYPE)); register(getNativeType(JSTypeNative.ERROR_TYPE)); register(getNativeType(JSTypeNative.URI_ERROR_TYPE)); register(getNativeType(JSTypeNative.EVAL_ERROR_TYPE)); register(getNativeType(JSTypeNative.TYPE_ERROR_TYPE)); register(getNativeType(JSTypeNative.RANGE_ERROR_TYPE)); register(getNativeType(JSTypeNative.REFERENCE_ERROR_TYPE)); register(getNativeType(JSTypeNative.SYNTAX_ERROR_TYPE)); register(getNativeType(JSTypeNative.REGEXP_TYPE)); register(getNativeType(JSTypeNative.STRING_OBJECT_TYPE)); register(getNativeType(JSTypeNative.STRING_TYPE)); register(getNativeType(JSTypeNative.VOID_TYPE)); register(getNativeType(JSTypeNative.VOID_TYPE), "Undefined"); register(getNativeType(JSTypeNative.VOID_TYPE), "void"); register(getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE), "Function"); } private void register(JSType type) { register(type, type.toString()); } private void register(JSType type, String name) { Preconditions.checkArgument( !name.contains("<"), "Type names cannot contain template annotations."); namesToTypes.put(name, type); // Add all the namespaces in which this name lives. while (name.indexOf('.') > 0) { name = name.substring(0, name.lastIndexOf('.')); namespaces.add(name); } } private void registerNativeType(JSTypeNative typeId, JSType type) { nativeTypes[typeId.ordinal()] = type; } /** * Tells the type system that {@code owner} may have a property named * {@code propertyName}. This allows the registry to keep track of what * types a property is defined

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>contains(name); } /** * Looks up a type by name. * * @param jsTypeName The name string. * @return the corresponding JSType object or {@code null} it cannot be found */ public JSType getType(String jsTypeName) { // TODO(user): Push every local type name out of namesToTypes so that // NamedType#resolve is correct. TemplateType templateType = templateTypes.get(jsTypeName); if (templateType != null) { return templateType; } return namesToTypes.get(jsTypeName); } public JSType getNativeType(JSTypeNative typeId) { return nativeTypes[typeId.ordinal()]; } public ObjectType getNativeObjectType(JSTypeNative typeId) { return (ObjectType) getNativeType(typeId); } public FunctionType getNativeFunctionType(JSTypeNative typeId) { return (FunctionType) getNativeType(typeId); } /** * Looks up a type by name. To allow for forward references to types, an * unrecognized string has to be bound to a NamedType object that will be * resolved later. * * @param scope A scope for doing type name resolution. * @param jsTypeName The name string. * @param sourceName The name of the source file where this reference appears. * @param lineno The line number of the reference. * @return a NamedType if the string argument is not one of the known types, * otherwise the corresponding JSType object. */ public JSType getType(StaticScope<JSType> scope, String jsTypeName, String sourceName, int lineno, int charno) { // Resolve template type names JSType type = null; JSType thisType = null; if (scope != null && scope.getTypeOfThis() != null) { thisType = scope.getTypeOfThis().toObjectType(); } if (thisType != null) { type = thisType.getTemplateTypeMap().getTemplateTypeKeyByName(jsTypeName); if (type != null) { Preconditions.checkState(type.isTemplateType(), "expected:" + type); return type; } } type = getType(jsTypeName); if (type == null) { // TODO(user): Each instance should support named type creation using // interning. NamedType namedType = new NamedType(this, jsTypeName, sourceName, lineno, charno); unresolvedNamedTypes.put(scope, namedType); type = namedType; } return type; } /** * Flushes out the current resolved and unresolved Named Types from * the type registry. This is intended to be used ONLY before a * compile is run. */ public void clearNamedTypes() { resolvedNamedTypes.clear(); unresolvedNamedTypes.clear(); } /** * Resolve all the unresolved types in the given scope. */ public void resolveTypesInScope(StaticScope<JSType> scope) { for (NamedType type : unresolvedNamedTypes.get(scope)) { type.resolve(reporter, scope); } resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope)); if (scope

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> != null && scope.getParentScope() == null) { // By default, the global "this" type is just an anonymous object. // If the user has defined a Window type, make the Window the // implicit prototype of "this". PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType( JSTypeNative.GLOBAL_THIS); JSType windowType = getType("Window"); if (globalThis.isUnknownType()) { ObjectType windowObjType = ObjectType.cast(windowType); if (windowObjType != null) { globalThis.setImplicitPrototype(windowObjType); } else { globalThis.setImplicitPrototype( getNativeObjectType(JSTypeNative.OBJECT_TYPE)); } } } } /** * Creates a type representing optional values of the given type. * @return the union of the type and the void type */ public JSType createOptionalType(JSType type) { if (type instanceof UnknownType || type.isAllType()) { return type; } else { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE)); } } /** * Creates a type representing nullable values of the given type. * @return the union of the type and the Null type */ public JSType createDefaultObjectUnion(JSType type) { if (type.isTemplateType()) { // Template types represent the substituted type exactly and should // not be wrapped. return type; } else { return shouldTolerateUndefinedValues() ? createOptionalNullableType(type) : createNullableType(type); } } /** * Creates a type representing nullable values of the given type. * @return the union of the type and the Null type */ public JSType createNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a nullable and undefine-able value of the given type. * @return The union of the type and null and undefined. */ public JSType createOptionalNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE), getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a union type whose variants are the arguments. */ public JSType createUnionType(JSType... variants) { UnionTypeBuilder builder = new UnionTypeBuilder(this); for (JSType type : variants) { builder.addAlternate(type); } return builder.build(); } /** * Creates a union type whose variants are the built-in types specified * by the arguments. */ public JSType createUnionType(JSTypeNative... variants) { UnionTypeBuilder builder = new UnionTypeBuilder(this); for (JSTypeNative typeId : variants) { builder.addAlternate(getNativeType(typeId)); } return builder.build(); } /** * Creates an enum type. */ public EnumType createEnumType( String name, Node source, JSType elementsType) { return new EnumType(this, name, source, elementsType); } /** * Creates

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>getUnfilledTemplateKeys()) { JSType templatizedType = templatizedTypes.containsKey(key) ? templatizedTypes.get(key) : getNativeType(UNKNOWN_TYPE); builder.add(templatizedType); } return createTemplatizedType(baseType, builder.build()); } /** * Creates a templatized instance of the specified type. Only ObjectTypes * can currently be templatized; extend the logic in this function when * more types can be templatized. * @param baseType the type to be templatized. * @param templatizedTypes a list of the template JSTypes. Will be matched by * list order to the template keys on the base type. */ public TemplatizedType createTemplatizedType( ObjectType baseType, JSType... templatizedTypes) { return createTemplatizedType( baseType, ImmutableList.copyOf(templatizedTypes)); } /** * Creates a named type. */ @VisibleForTesting public JSType createNamedType(String reference, String sourceName, int lineno, int charno) { return new NamedType(this, reference, sourceName, lineno, charno); } /** * Identifies the name of a typedef or enum before we actually declare it. */ public void identifyNonNullableName(String name) { Preconditions.checkNotNull(name); nonNullableTypeNames.add(name); } /** * Creates a JSType from the nodes representing a type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ public JSType createFromTypeNodes(Node n, String sourceName, StaticScope<JSType> scope) { if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) { // If the type expression doesn't contain any names, just // resolve it anyway. boolean hasNames = hasTypeName(n); if (hasNames) { return new UnresolvedTypeExpression(this, n, sourceName); } } return createFromTypeNodesInternal(n, sourceName, scope); } private boolean hasTypeName(Node n) { if (n.getType() == Token.STRING) { return true; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (hasTypeName(child)) { return true; } } return false; } /** @see #createFromTypeNodes(Node, String, StaticScope) */ private JSType createFromTypeNodesInternal(Node n, String sourceName, StaticScope<JSType> scope) { switch (n.getType()) { case Token.LC: // Record type. return createRecordTypeFromNodes( n.getFirstChild(), sourceName, scope); case Token.BANG: // Not nullable return createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope) .restrictByNotNullOrUndefined(); case Token.QMARK: // Nullable or unknown Node firstChild = n.getFirstChild(); if (firstChild == null) { return getNativeType(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>UNKNOWN_TYPE); } return createDefaultObjectUnion( createFromTypeNodesInternal( firstChild, sourceName, scope)); case Token.EQUALS: // Optional return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope)); case Token.ELLIPSIS: // Var args return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope)); case Token.STAR: // The AllType return getNativeType(ALL_TYPE); case Token.LB: // Array type // TODO(nicksantos): Enforce membership restrictions on the Array. return getNativeType(ARRAY_TYPE); case Token.PIPE: // Union type UnionTypeBuilder builder = new UnionTypeBuilder(this); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { builder.addAlternate( createFromTypeNodesInternal(child, sourceName, scope)); } return builder.build(); case Token.EMPTY: // When the return value of a function is not specified return getNativeType(UNKNOWN_TYPE); case Token.VOID: // Only allowed in the return value of a function. return getNativeType(VOID_TYPE); case Token.STRING: JSType namedType = getType(scope, n.getString(), sourceName, n.getLineno(), n.getCharno()); if (resolveMode != ResolveMode.LAZY_NAMES) { namedType = namedType.resolveInternal(reporter, scope); } if ((namedType instanceof ObjectType) && !(nonNullableTypeNames.contains(n.getString()))) { Node typeList = n.getFirstChild(); int nAllowedTypes = namedType.getTemplateTypeMap().numUnfilledTemplateKeys(); if (typeList != null && nAllowedTypes > 0) { // Templatized types. ImmutableList.Builder<JSType> templateTypes = ImmutableList.builder(); // Special case for Object, where Object.<X> implies Object.<?,X>. if (n.getString().equals("Object") && typeList.getFirstChild() == typeList.getLastChild()) { templateTypes.add(getNativeType(UNKNOWN_TYPE)); } int templateNodeIndex = 0; for (Node templateNode : typeList.getFirstChild().siblings()) { // Don't parse more templatized type nodes than the type can // accommodate. This is because some existing clients have // template annotations on non-templatized classes, for instance: // goog.structs.Set.<SomeType> // The problem in these cases is that the previously-unparsed // SomeType is not actually a valid type. To prevent these clients // from seeing unknown type errors, we explicitly don't parse // these types. // TODO(user): Address this issue by removing bad template // annotations on non-templatized classes. if (++templateNodeIndex > nAllowedTypes) { break; } templateTypes.add(createFromTypeNodesInternal( templateNode, sourceName, scope)); } namedType = createTemplatizedType( (ObjectType) namedType, templateTypes.build()); Preconditions.checkNotNull(namedType); }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> return createDefaultObjectUnion(namedType); } else { return namedType; } case Token.FUNCTION: ObjectType thisType = null; boolean isConstructor = false; Node current = n.getFirstChild(); if (current.getType() == Token.THIS || current.getType() == Token.NEW) { Node contextNode = current.getFirstChild(); thisType = ObjectType.cast( createFromTypeNodesInternal( contextNode, sourceName, scope) .restrictByNotNullOrUndefined()); if (thisType == null) { reporter.warning( SimpleErrorReporter.getMessage0( current.getType() == Token.THIS ? "msg.jsdoc.function.thisnotobject" : "msg.jsdoc.function.newnotobject"), sourceName, contextNode.getLineno(), contextNode.getCharno()); } isConstructor = current.getType() == Token.NEW; current = current.getNext(); } FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this); if (current.getType() == Token.PARAM_LIST) { for (Node arg = current.getFirstChild(); arg != null; arg = arg.getNext()) { if (arg.getType() == Token.ELLIPSIS) { if (arg.getChildCount() == 0) { paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE)); } else { paramBuilder.addVarArgs( createFromTypeNodesInternal( arg.getFirstChild(), sourceName, scope)); } } else { JSType type = createFromTypeNodesInternal( arg, sourceName, scope); if (arg.getType() == Token.EQUALS) { boolean addSuccess = paramBuilder.addOptionalParams(type); if (!addSuccess) { reporter.warning( SimpleErrorReporter.getMessage0( "msg.jsdoc.function.varargs"), sourceName, arg.getLineno(), arg.getCharno()); } } else { paramBuilder.addRequiredParams(type); } } } current = current.getNext(); } JSType returnType = createFromTypeNodesInternal(current, sourceName, scope); return new FunctionBuilder(this) .withParams(paramBuilder) .withReturnType(returnType) .withTypeOfThis(thisType) .setIsConstructor(isConstructor) .build(); } throw new IllegalStateException( "Unexpected node in type expression: " + n.toString()); } /** * Creates a RecordType from the nodes representing said record type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ private JSType createRecordTypeFromNodes(Node n, String sourceName, StaticScope<JSType> scope) { RecordTypeBuilder builder = new RecordTypeBuilder(this); // For each of the fields in the record type. for (Node fieldTypeNode = n.getFirstChild(); fieldTypeNode != null; fieldTypeNode = fieldTypeNode.getNext()) { // Get the property's name. Node fieldNameNode = fieldTypeNode; boolean hasType = false; if (

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>fieldTypeNode.getType() == Token.COLON) { fieldNameNode = fieldTypeNode.getFirstChild(); hasType = true; } String fieldName = fieldNameNode.getString(); // TODO(user): Move this into the lexer/parser. // Remove the string literal characters around a field name, // if any. if (fieldName.startsWith("'") || fieldName.startsWith("\"")) { fieldName = fieldName.substring(1, fieldName.length() - 1); } // Get the property's type. JSType fieldType = null; if (hasType) { // We have a declared type. fieldType = createFromTypeNodesInternal( fieldTypeNode.getLastChild(), sourceName, scope); } else { // Otherwise, the type is UNKNOWN. fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE); } // Add the property to the record. if (builder.addProperty(fieldName, fieldType, fieldNameNode) == null) { // Duplicate field name, warning and skip reporter.warning( "Duplicate record field " + fieldName, sourceName, n.getLineno(), fieldNameNode.getCharno()); } } return builder.build(); } /** * Sets the template type name. */ public void setTemplateTypeNames(List<TemplateType> keys) { Preconditions.checkNotNull(keys); for (TemplateType key : keys) { templateTypes.put(key.getReferenceName(), key); } } /** * Clears the template type name. */ public void clearTemplateTypeNames() { templateTypes.clear(); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.javascript.rhino.JSDocInfo; import java.io.Serializable; /** * The minimum implementation of StaticSlot<JSType>. * * @author nicksantos@google.com (Nick Santos) */ public class SimpleSlot implements StaticSlot<JSType>, Serializable { private static final long serialVersionUID = 1L; final String name; final JSType type; final boolean inferred; public SimpleSlot(String name, JSType type, boolean inferred) { this.name = name; this.type = type; this.inferred = inferred; } @Override public String getName() { return name; } @Override public JSType getType() { return type; } @Override public boolean isTypeInferred() { return inferred; } @Override public StaticReference<JSType> getDeclaration() { return null; } @Override public JSDocInfo getJSDocInfo() { return null; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> recorded */ public boolean recordNoTypeCheck() { if (!currentInfo.isNoTypeCheck()) { currentInfo.setNoCheck(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isConstructor()} flag set to {@code true}. * * @return {@code true} if the constructor was recorded and {@code false} * if it was already defined or it was incompatible with the existing * flags */ public boolean recordConstructor() { if (!hasAnySingletonTypeTags() && !currentInfo.isConstructor() && !currentInfo.isInterface()) { currentInfo.setConstructor(true); populated = true; return true; } else { return false; } } /** * Whether the {@link JSDocInfo} being built will have its * {@link JSDocInfo#isConstructor()} flag set to {@code true}. */ public boolean isConstructorRecorded() { return currentInfo.isConstructor(); } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#makesStructs()} flag set to {@code true}. * * @return {@code true} if the struct was recorded and {@code false} * if it was already defined or it was incompatible with the existing flags */ public boolean recordStruct() { if (hasAnySingletonTypeTags() || currentInfo.isInterface() || currentInfo.makesDicts() || currentInfo.makesStructs()) { return false; } currentInfo.setStruct(); populated = true; return true; } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#makesDicts()} flag set to {@code true}. * * @return {@code true} if the dict was recorded and {@code false} * if it was already defined or it was incompatible with the existing flags */ public boolean recordDict() { if (hasAnySingletonTypeTags() || currentInfo.isInterface() || currentInfo.makesDicts() || currentInfo.makesStructs()) { return false; } currentInfo.setDict(); populated = true; return true; } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isJavaDispatch()} flag set to {@code true}. * * @return {@code true} if the javadispatch was recorded and {@code false} * if it was already defined or it was incompatible with the existing * flags */ public boolean recordJavaDispatch() { if (!currentInfo.isJavaDispatch()) { currentInfo.setJavaDispatch(true); populated = true; return true; } else { return false; } } /** * Whether the {@link JSDocInfo} being built will have its * {@link JSDocInfo#isJavaDispatch()} flag set to {@code true}. */ public boolean isJavaDispatch

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>() { return currentInfo.isJavaDispatch(); } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#shouldPreserveTry()} flag set to {@code true}. */ public boolean recordPreserveTry() { if (!currentInfo.shouldPreserveTry()) { currentInfo.setShouldPreserveTry(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isOverride()} flag set to {@code true}. */ public boolean recordOverride() { if (!currentInfo.isOverride()) { currentInfo.setOverride(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isNoAlias()} flag set to {@code true}. */ public boolean recordNoAlias() { if (!currentInfo.isNoAlias()) { currentInfo.setNoAlias(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isDeprecated()} flag set to {@code true}. */ public boolean recordDeprecated() { if (!currentInfo.isDeprecated()) { currentInfo.setDeprecated(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isInterface()} flag set to {@code true}. * * @return {@code true} if the flag was recorded and {@code false} * if it was already defined or it was incompatible with the existing flags */ public boolean recordInterface() { if (hasAnySingletonTypeTags() || currentInfo.makesStructs() || currentInfo.makesDicts() || currentInfo.isConstructor() || currentInfo.isInterface()) { return false; } currentInfo.setInterface(true); populated = true; return true; } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isExport()} flag set to {@code true}. */ public boolean recordExport() { if (!currentInfo.isExport()) { currentInfo.setExport(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isExpose()} flag set to {@code true}. */ public boolean recordExpose() { if (!currentInfo.isExpose()) { currentInfo.setExpose(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>#isNoShadow()} flag set to {@code true}. */ public boolean recordNoShadow() { if (!currentInfo.isNoShadow()) { currentInfo.setNoShadow(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isImplicitCast()} flag set to {@code true}. */ public boolean recordImplicitCast() { if (!currentInfo.isImplicitCast()) { currentInfo.setImplicitCast(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isNoSideEffects()} flag set to {@code true}. */ public boolean recordNoSideEffects() { if (!hasAnySingletonSideEffectTags() && !currentInfo.isNoSideEffects()) { currentInfo.setNoSideEffects(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isExterns()} flag set to {@code true}. */ public boolean recordExterns() { if (!currentInfo.isExterns()) { currentInfo.setExterns(true); populated = true; return true; } else { return false; } } /** * Whether the {@link JSDocInfo} being built will have its * {@link JSDocInfo#isInterface()} flag set to {@code true}. */ public boolean isInterfaceRecorded() { return currentInfo.isInterface(); } /** * @return Whether a parameter of the given name has already been recorded. */ public boolean hasParameter(String name) { return currentInfo.hasParameter(name); } /** * Records an implemented interface. */ public boolean recordImplementedInterface(JSTypeExpression interfaceName) { if (currentInfo.addImplementedInterface(interfaceName)) { populated = true; return true; } else { return false; } } /** * Records an extended interface type. */ public boolean recordExtendedInterface(JSTypeExpression interfaceType) { if (currentInfo.addExtendedInterface(interfaceType)) { populated = true; return true; } else { return false; } } /** * Records that we're lending to another name. */ public boolean recordLends(String name) { if (!hasAnyTypeRelatedTags()) { currentInfo.setLendsName(name); populated = true; return true; } else { return false; } } /** * Returns whether current JSDoc is annotated with {@code @ngInject}. */ public boolean isNgInjectRecorded() { return currentInfo.isNgInject(); } /** * Records that we'd like to add {@code $inject} property inferred from * parameters. */ public boolean recordNgInject(boolean ngInject)

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { if (!isNgInjectRecorded()) { currentInfo.setNgInject(ngInject); populated = true; return true; } else { return false; } } /** * Returns whether current JSDoc is annotated with {@code @wizaction}. */ public boolean isWizactionRecorded() { return currentInfo.isWizaction(); } /** * Records that this method is to be exposed as a wizaction. */ public boolean recordWizaction() { if (!isWizactionRecorded()) { currentInfo.setWizaction(true); populated = true; return true; } else { return false; } } /** * Returns whether current JSDoc is annotated with {@code @disposes}. */ public boolean isDisposesRecorded() { return currentInfo.isDisposes(); } /** * Records a parameter that gets disposed. * * @return {@code true} if all the parameters was recorded and * {@code false} if a parameter with the same name was already defined */ public boolean recordDisposesParameter(List<String> parameterNames) { for (String parameterName : parameterNames) { if ((currentInfo.hasParameter(parameterName) || parameterName.equals("*")) && currentInfo.setDisposedParameter(parameterName)) { populated = true; } else { return false; } } return true; } /** * Whether the current doc info has other type tags, like * {@code @param} or {@code @return} or {@code @type} or etc. */ private boolean hasAnyTypeRelatedTags() { return currentInfo.isConstructor() || currentInfo.isInterface() || currentInfo.getParameterCount() > 0 || currentInfo.hasReturnType() || currentInfo.hasBaseType() || currentInfo.getExtendedInterfacesCount() > 0 || currentInfo.getLendsName() != null || currentInfo.hasThisType() || hasAnySingletonTypeTags(); } /** * Whether the current doc info has any of the singleton type * tags that may not appear with other type tags, like * {@code @type} or {@code @typedef}. */ private boolean hasAnySingletonTypeTags() { return currentInfo.hasType() || currentInfo.hasTypedefType() || currentInfo.hasEnumParameterType(); } /** * Whether the current doc info has any of the singleton type * tags that may not appear with other type tags, like * {@code @type} or {@code @typedef}. */ private boolean hasAnySingletonSideEffectTags() { return currentInfo.isNoSideEffects() || currentInfo.hasModifies(); } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> @return a comparator or null (in particular, if not overridden) */ public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator( boolean isForward) { return null; } /** * The edge object for the control flow graph. */ public static enum Branch { /** Edge is taken if the condition is true. */ ON_TRUE, /** Edge is taken if the condition is false. */ ON_FALSE, /** Unconditional branch. */ UNCOND, /** * Exception-handling code paths. * Conflates two kind of control flow passing: * - An exception is thrown, and falls into a catch or finally block * - During exception handling, a finally block finishes and control * passes to the next finally block. * In theory, we need 2 different edge types. In practice, we * can just treat them as "the edges we can't really optimize". */ ON_EX, /** Possible folded-away template */ SYN_BLOCK; public boolean isConditional() { return this == ON_TRUE || this == ON_FALSE; } } /** * Abstract callback to visit a control flow graph node without going into * subtrees of the node that are also represented by other * control flow graph nodes. * * <p>For example, traversing an IF node as root will visit the two subtrees * pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and * {@link ControlFlowGraph.Branch#ON_FALSE} edges. */ public abstract static class AbstractCfgNodeTraversalCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (parent == null) { return true; } return !isEnteringNewCfgNode(n); } } /** * @return True if n should be represented by a new CFG node in the control * flow graph. */ public static boolean isEnteringNewCfgNode(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.SCRIPT: case Token.TRY: return true; case Token.FUNCTION: // A function node represents the start of a function where the name // bleeds into the local scope and parameters are assigned // to the formal argument names. The node includes the name of the // function and the LP list since we assume the whole set up process // is atomic without change in control flow. The next change of // control is going into the function's body, represented by the second // child. return n != parent.getFirstChild().getNext(); case Token.WHILE: case Token.DO: case Token.IF: // These control structures are represented by a node that holds the // condition. Each of them is a branch node based on its condition. return NodeUtil.getConditionExpression(parent) != n; case Token.FOR: // The FOR(;;) node differs from other control structures in that // it has an initialization and an increment statement. Those // two statements have corresponding CFG nodes to represent them. // The FOR node only represents the condition check for each iteration. //

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> or returns null if this is not * a function. */ public TemplateType toMaybeTemplateType() { return null; } /** * Null-safe version of toMaybeTemplateType(). */ public static TemplateType toMaybeTemplateType(JSType type) { return type == null ? null : type.toMaybeTemplateType(); } public boolean hasAnyTemplateTypes() { if (!this.inTemplatedCheckVisit) { this.inTemplatedCheckVisit = true; boolean result = hasAnyTemplateTypesInternal(); this.inTemplatedCheckVisit = false; return result; } else { // prevent infinite recursion, this is "not yet". return false; } } boolean hasAnyTemplateTypesInternal() { return templateTypeMap.hasAnyTemplateTypesInternal(); } /** * Returns the template type map associated with this type. */ public TemplateTypeMap getTemplateTypeMap() { return templateTypeMap; } /** * Extends the template type map associated with this type, merging in the * keys and values of the specified map. */ public void extendTemplateTypeMap(TemplateTypeMap otherMap) { templateTypeMap = templateTypeMap.extend(otherMap); } /** * Tests whether this type is an {@code Object}, or any subtype thereof. * @return {@code this &lt;: Object} */ public boolean isObject() { return false; } /** * Whether this type is a {@link FunctionType} that is a constructor or a * named type that points to such a type. */ public boolean isConstructor() { return false; } /** * Whether this type is a nominal type (a named instance object or * a named enum). */ public boolean isNominalType() { return false; } /** * Whether this type is the original constructor of a nominal type. * Does not include structural constructors. */ public final boolean isNominalConstructor() { if (isConstructor() || isInterface()) { FunctionType fn = toMaybeFunctionType(); if (fn == null) { return false; } // Programmer-defined constructors will have a link // back to the original function in the source tree. // Structural constructors will not. if (fn.getSource() != null) { return true; } // Native constructors are always nominal. return fn.isNativeObjectType(); } return false; } /** * Whether this type is an Instance object of some constructor. * Does not necessarily mean this is an {@link InstanceObjectType}. */ public boolean isInstanceType() { return false; } /** * Whether this type is a {@link FunctionType} that is an interface or a named * type that points to such a type. */ public boolean isInterface() { return false; } /** * Whether this type is a {@link FunctionType} that is an ordinary function or * a named type that points to such a type. */ public boolean isOrdinaryFunction() { return false; } /** * Checks if two types are equivalent. */ public final boolean isEquivalentTo(JSType that) { return checkEquivalenceHelper(that, EquivalenceMethod.IDENTITY); }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> TemplateTypeMap thatTypeParams = thatType.getTemplateTypeMap(); boolean templateMatch = true; if (isExemptFromTemplateTypeInvariance(thatType)) { // Array and Object are exempt from template type invariance; their // template types maps are considered a match only if the ObjectElementKey // values are subtypes/supertypes of one another. TemplateType key = thisType.registry.getObjectElementKey(); JSType thisElement = thisTypeParams.getTemplateType(key); JSType thatElement = thatTypeParams.getTemplateType(key); templateMatch = thisElement.isSubtype(thatElement) || thatElement.isSubtype(thisElement); } else { templateMatch = thisTypeParams.checkEquivalenceHelper( thatTypeParams, EquivalenceMethod.INVARIANT); } if (!templateMatch) { return false; } // Templatized types. The above check guarantees TemplateTypeMap // equivalence; check if the base type is a subtype. if (thisType.isTemplatizedType()) { return thisType.toMaybeTemplatizedType().getReferencedType().isSubtype( thatType); } // proxy types if (thatType instanceof ProxyObjectType) { return thisType.isSubtype( ((ProxyObjectType) thatType).getReferencedTypeInternal()); } return false; } /** * Determines if the specified type is exempt from standard invariant * templatized typing rules. */ static boolean isExemptFromTemplateTypeInvariance(JSType type) { ObjectType objType = type.toObjectType(); return objType == null || "Array".equals(objType.getReferenceName()) || "Object".equals(objType.getReferenceName()); } /** * Visit this type with the given visitor. * @see com.google.javascript.rhino.jstype.Visitor * @return the value returned by the visitor */ public abstract <T> T visit(Visitor<T> visitor); /** * Visit the types with the given visitor. * @see com.google.javascript.rhino.jstype.RelationshipVisitor * @return the value returned by the visitor */ abstract <T> T visit(RelationshipVisitor<T> visitor, JSType that); /** * Force this type to resolve, even if the registry is in a lazy * resolving mode. * @see #resolve */ public final JSType forceResolve(ErrorReporter t, StaticScope<JSType> scope) { ResolveMode oldResolveMode = registry.getResolveMode(); registry.setResolveMode(ResolveMode.IMMEDIATE); JSType result = resolve(t, scope); registry.setResolveMode(oldResolveMode); return result; } /** * Resolve this type in the given scope. * * The returned value must be equal to {@code this}, as defined by * {@link #isEquivalentTo}. It may or may not be the same object. This method * may modify the internal state of {@code this}, as long as it does * so in a way that preserves Object equality. * * For efficiency, we should only resolve a type once per compilation job. * For incremental compilations, one compilation job may need

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> the * artifacts from a previous generation, so we will eventually need * a generational flag instead of a boolean one. */ public final JSType resolve(ErrorReporter t, StaticScope<JSType> scope) { if (resolved) { // TODO(nicksantos): Check to see if resolve() looped back on itself. // Preconditions.checkNotNull(resolveResult); if (resolveResult == null) { return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); } return resolveResult; } resolved = true; resolveResult = resolveInternal(t, scope); resolveResult.setResolvedTypeInternal(resolveResult); return resolveResult; } /** * @see #resolve */ abstract JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope); void setResolvedTypeInternal(JSType type) { resolveResult = type; resolved = true; } /** Whether the type has been resolved. */ public final boolean isResolved() { return resolved; } /** Clears the resolved field. */ public final void clearResolved() { resolved = false; resolveResult = null; } /** * A null-safe resolve. * @see #resolve */ static final JSType safeResolve( JSType type, ErrorReporter t, StaticScope<JSType> scope) { return type == null ? null : type.resolve(t, scope); } /** * Certain types have constraints on them at resolution-time. * For example, a type in an {@code @extends} annotation must be an * object. Clients should inject a validator that emits a warning * if the type does not validate, and return false. */ public boolean setValidator(Predicate<JSType> validator) { return validator.apply(this); } public static class TypePair { public final JSType typeA; public final JSType typeB; public TypePair(JSType typeA, JSType typeB) { this.typeA = typeA; this.typeB = typeB; } } /** * A string representation of this type, suitable for printing * in warnings. */ @Override public String toString() { return toStringHelper(false); } /** * A hash code function for diagnosing complicated issues * around type-identity. */ public String toDebugHashCodeString() { return "{" + hashCode() + "}"; } /** * A string representation of this type, suitable for printing * in type annotations at code generation time. */ public final String toAnnotationString() { return toStringHelper(true); } /** * @param forAnnotations Whether this is for use in code generator * annotations. Otherwise, it's for warnings. */ abstract String toStringHelper(boolean forAnnotations); /** * Modify this type so that it matches the specified type. * * This is useful for reverse type-inference, where we want to * infer that an object literal matches its constraint (much like * how the java compiler does reverse-inference to figure out generics). * @param constraint */ public void matchConstraint(JSType constraint) {} }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> getPossibleToBooleanOutcomes() { return BooleanLiteralSet.TRUE; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { returnType = safeResolve(returnType, t, scope); if (parameters != null) { for (Node paramNode = parameters.getFirstChild(); paramNode != null; paramNode = paramNode.getNext()) { paramNode.setJSType(paramNode.getJSType().resolve(t, scope)); } } return this; } boolean hasUnknownParamsOrReturn() { if (parameters != null) { for (Node paramNode = parameters.getFirstChild(); paramNode != null; paramNode = paramNode.getNext()) { JSType type = paramNode.getJSType(); if (type == null || type.isUnknownType()) { return true; } } } return returnType == null || returnType.isUnknownType(); } @Override String toStringHelper(boolean forAnnotations) { return "[ArrowType]"; } @Override public boolean hasAnyTemplateTypesInternal() { return returnType.hasAnyTemplateTypes() || hasTemplatedParameterType(); } private boolean hasTemplatedParameterType() { if (parameters != null) { for (Node paramNode = parameters.getFirstChild(); paramNode != null; paramNode = paramNode.getNext()) { JSType type = paramNode.getJSType(); if (type != null && type.hasAnyTemplateTypes()) { return true; } } } return false; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> description found on that annotation and, if applicable, * the type declaration. All this information is only collected * if documentation collection is turned on. */ public static final class Marker { private TrimmedStringPosition annotation = null; private TrimmedStringPosition name = null; private SourcePosition<Node> nameNode = null; private StringPosition description = null; private TypePosition type = null; /** * Gets the position information for the annotation name. (e.g., "param") */ public StringPosition getAnnotation() { return annotation; } void setAnnotation(TrimmedStringPosition p) { annotation = p; } /** * Gets the position information for the name found * in a @param tag. * @deprecated Use #getNameNode */ @Deprecated public StringPosition getName() { return name; } void setName(TrimmedStringPosition p) { name = p; } /** * Gets the position information for the name found * in an @param tag. */ public SourcePosition<Node> getNameNode() { return nameNode; } void setNameNode(SourcePosition<Node> p) { nameNode = p; } /** * Gets the position information for the description found * in a block tag. */ public StringPosition getDescription() { return description; } void setDescription(StringPosition p) { description = p; } /** * Gets the position information for the type expression found * in some block tags, like "@param" and "@return". */ public TypePosition getType() { return type; } void setType(TypePosition p) { type = p; } } private LazilyInitializedInfo info = null; private LazilyInitializedDocumentation documentation = null; // The Node this JSDoc is associated with. private Node associatedNode = null; private Visibility visibility = null; /** * The {@link #isConstant()}, {@link #isConstructor()}, {@link #isInterface}, * {@link #isHidden()} and {@link #shouldPreserveTry()} flags as well as * whether the {@link #type} field stores a value for {@link #getType()}, * {@link #getReturnType()} or {@link #getEnumParameterType()}. * * @see #setFlag(boolean, int) * @see #getFlag(int) * @see #setType(JSTypeExpression, int) * @see #getType(int) */ private int bitset = 0x00; /** * The type for {@link #getType()}, {@link #getReturnType()} or * {@link #getEnumParameterType()}. The knowledge of which one is recorded is * stored in the {@link #bitset} field. * * @see #setType(JSTypeExpression, int) * @see #getType(int) */ private JSTypeExpression type = null; /** * The type for {@link #getThisType()}. */ private JSTypeExpression thisType = null; /** * Whether to include documentation. * * @see JSDocInfo.LazilyInitializedDocumentation */ private boolean includeDocumentation = false; /** * Position of the original comment

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>) { setFlag(value, MASK_IMPLICITCAST); } void setNoSideEffects(boolean value) { setFlag(value, MASK_NOSIDEEFFECTS); } void setExterns(boolean value) { setFlag(value, MASK_EXTERNS); } void setJavaDispatch(boolean value) { setFlag(value, MASK_JAVADISPATCH); } void setNoCompile(boolean value) { setFlag(value, MASK_NOCOMPILE); } private void setFlag(boolean value, int mask) { if (value) { bitset |= mask; } else { bitset &= ~mask; } } /** * @return whether the {@code @consistentIdGenerator} is present on * this {@link JSDocInfo} */ public boolean isConsistentIdGenerator() { return getFlag(MASK_CONSISTIDGEN); } /** * @return whether the {@code @stableIdGenerator} is present on this {@link JSDocInfo}. */ public boolean isStableIdGenerator() { return getFlag(MASK_STALBEIDGEN); } /** * @return whether the {@code @stableIdGenerator} is present on this {@link JSDocInfo}. */ public boolean isMappedIdGenerator() { return getFlag(MASK_MAPPEDIDGEN); } /** * Returns whether the {@code @const} annotation is present on this * {@link JSDocInfo}. */ public boolean isConstant() { return getFlag(MASK_CONSTANT) || isDefine(); } /** * Returns whether the {@code @constructor} annotation is present on this * {@link JSDocInfo}. */ public boolean isConstructor() { return getFlag(MASK_CONSTRUCTOR); } /** * Returns whether the {@code @struct} annotation is present on this * {@link JSDocInfo}. */ public boolean makesStructs() { return getFlag(MASK_STRUCT); } /** * Returns whether the {@code @dict} annotation is present on this * {@link JSDocInfo}. */ public boolean makesDicts() { return getFlag(MASK_DICT); } /** * Returns whether the {@code @define} annotation is present on this * {@link JSDocInfo}. If this annotation is present, then the * {@link #getType()} method will retrieve the define type. */ public boolean isDefine() { return getFlag(MASK_DEFINE); } /** * Returns whether the {@code @hidden} annotation is present on this * {@link JSDocInfo}. */ public boolean isHidden() { return getFlag(MASK_HIDDEN); } /** * Returns whether the {@code @nocheck} annotation is present on this * {@link JSDocInfo}. */ public boolean isNoTypeCheck() { return getFlag(MASK_NOCHECK); } /** * Returns whether the {@code @preserveTry} annotation is present on this * {@link JSDocInfo}. */ public boolean shouldPreserveTry() { return getFlag(MASK_PRESERVETRY); } /**

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> * Returns whether the {@code @override} annotation is present on this * {@link JSDocInfo}. */ public boolean isOverride() { return getFlag(MASK_OVERRIDE); } /** * Returns whether the {@code @noalias} annotation is present on this * {@link JSDocInfo}. */ public boolean isNoAlias() { return getFlag(MASK_NOALIAS); } /** * Returns whether the {@code @deprecated} annotation is present on this * {@link JSDocInfo}. */ public boolean isDeprecated() { return getFlag(MASK_DEPRECATED); } /** * Returns whether the {@code @interface} annotation is present on this * {@link JSDocInfo}. */ public boolean isInterface() { return getFlag(MASK_INTERFACE); } /** * Returns whether the {@code @export} annotation is present on this * {@link JSDocInfo}. */ public boolean isExport() { return getFlag(MASK_EXPORT); } /** * Returns whether the {@code @expose} annotation is present on this * {@link JSDocInfo}. */ public boolean isExpose() { return getFlag(MASK_EXPOSE); } /** * Returns whether the {@code @noshadow} annotation is present on this * {@link JSDocInfo}. */ public boolean isNoShadow() { return getFlag(MASK_NOSHADOW); } /** * @return whether the {@code @idGenerator} is present on * this {@link JSDocInfo} */ public boolean isIdGenerator() { return getFlag(MASK_IDGEN); } /** * Returns whether the {@code @implicitCast} annotation is present on this * {@link JSDocInfo}. */ public boolean isImplicitCast() { return getFlag(MASK_IMPLICITCAST); } /** * Returns whether the {@code @nosideeffects} annotation is present on this * {@link JSDocInfo}. */ public boolean isNoSideEffects() { return getFlag(MASK_NOSIDEEFFECTS); } /** * Returns whether the {@code @externs} annotation is present on this * {@link JSDocInfo}. */ public boolean isExterns() { return getFlag(MASK_EXTERNS); } /** * Returns whether the {@code @javadispatch} annotation is present on this * {@link JSDocInfo}. */ public boolean isJavaDispatch() { return getFlag(MASK_JAVADISPATCH); } /** * Returns whether the {@code @nocompile} annotation is present on this * {@link JSDocInfo}. */ public boolean isNoCompile() { return getFlag(MASK_NOCOMPILE); } /** * @return Whether there is declaration present on this {@link JSDocInfo}. */ public boolean containsDeclaration() { return (hasType() || hasReturnType() || hasEnumParameterType() || hasTypedefType() || hasThisType() || getParameterCount() > 0 || getFlag(MASK_CONSTANT | MASK_CONSTRUCTOR | MASK_DEFINE

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> { return 0; } return info.parameters.size(); } void setType(JSTypeExpression type) { setType(type, TYPEFIELD_TYPE); } void setReturnType(JSTypeExpression type) { setType(type, TYPEFIELD_RETURN); } void setEnumParameterType(JSTypeExpression type) { setType(type, TYPEFIELD_ENUM); } void setTypedefType(JSTypeExpression type) { setType(type, TYPEFIELD_TYPEDEF); } private void setType(JSTypeExpression type, int mask) { if ((bitset & MASK_TYPEFIELD) != 0) { throw new IllegalStateException( "API tried to add two incompatible type tags. " + "This should have been blocked and emitted a warning."); } this.bitset = (bitset & MASK_FLAGS) | mask; this.type = type; } /** * Returns the list of thrown types. */ public List<JSTypeExpression> getThrownTypes() { if (info == null || info.thrownTypes == null) { return ImmutableList.of(); } return Collections.unmodifiableList(info.thrownTypes); } /** * Returns whether a type, specified using the {@code @type} annotation, is * present on this JSDoc. */ public boolean hasType() { return hasType(TYPEFIELD_TYPE); } /** * Returns whether an enum parameter type, specified using the {@code @enum} * annotation, is present on this JSDoc. */ public boolean hasEnumParameterType() { return hasType(TYPEFIELD_ENUM); } /** * Returns whether a typedef parameter type, specified using the * {@code @typedef} annotation, is present on this JSDoc. */ public boolean hasTypedefType() { return hasType(TYPEFIELD_TYPEDEF); } /** * Returns whether this {@link JSDocInfo} contains a type for {@code @return} * annotation. */ public boolean hasReturnType() { return hasType(TYPEFIELD_RETURN); } private boolean hasType(int mask) { return (bitset & MASK_TYPEFIELD) == mask; } /** * Gets the type specified by the {@code @type} annotation. */ public JSTypeExpression getType() { return getType(TYPEFIELD_TYPE); } /** * Gets the return type specified by the {@code @return} annotation. */ public JSTypeExpression getReturnType() { return getType(TYPEFIELD_RETURN); } /** * Gets the enum parameter type specified by the {@code @enum} annotation. */ public JSTypeExpression getEnumParameterType() { return getType(TYPEFIELD_ENUM); } /** * Gets the typedef type specified by the {@code @type} annotation. */ public JSTypeExpression getTypedefType() { return getType(TYPEFIELD_TYPEDEF); } private JSTypeExpression getType(int typefield) { if ((MASK_TYPEFIELD & bitset) == typefield) { return type; } else { return null; } } /** * Gets the type specified by the {@code @this} annotation

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> return propertyNode == null ? null : propertyNode.getStaticSourceFile(); } @Override public Property getSymbol() { return this; } @Override public Property getDeclaration() { return propertyNode == null ? null : this; } @Override public JSType getType() { return type; } @Override public boolean isTypeInferred() { return inferred; } boolean isFromExterns() { return propertyNode == null ? false : propertyNode.isFromExterns(); } void setType(JSType type) { this.type = type; } @Override public JSDocInfo getJSDocInfo() { return this.docInfo; } void setJSDocInfo(JSDocInfo info) { this.docInfo = info; } public void setNode(Node n) { this.propertyNode = n; } }

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> PropAccess { ANY, STRUCT, DICT } /** * {@code [[Call]]} property. */ private ArrowType call; /** * The {@code prototype} property. This field is lazily initialized by * {@code #getPrototype()}. The most important reason for lazily * initializing this field is that there are cycles in the native types * graph, so some prototypes must temporarily be {@code null} during * the construction of the graph. * * If non-null, the type must be a PrototypeObjectType. */ private Property prototypeSlot; /** * Whether a function is a constructor, an interface, or just an ordinary * function. */ private final Kind kind; /** * Whether the instances are structs, dicts, or unrestricted. */ private PropAccess propAccess; /** * The type of {@code this} in the scope of this function. */ private JSType typeOfThis; /** * The function node which this type represents. It may be {@code null}. */ private Node source; /** * The interfaces directly implemented by this function (for constructors) * It is only relevant for constructors. May not be {@code null}. */ private List<ObjectType> implementedInterfaces = ImmutableList.of(); /** * The interfaces directly extended by this function (for interfaces) * It is only relevant for constructors. May not be {@code null}. */ private List<ObjectType> extendedInterfaces = ImmutableList.of(); /** * The types which are subtypes of this function. It is only relevant for * constructors and may be {@code null}. */ private List<FunctionType> subTypes; /** Creates an instance for a function that might be a constructor. */ FunctionType(JSTypeRegistry registry, String name, Node source, ArrowType arrowType, JSType typeOfThis, TemplateTypeMap templateTypeMap, boolean isConstructor, boolean nativeType) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE), nativeType, templateTypeMap); setPrettyPrint(true); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkNotNull(arrowType); this.source = source; if (isConstructor) { this.kind = Kind.CONSTRUCTOR; this.propAccess = PropAccess.ANY; this.typeOfThis = typeOfThis != null ? typeOfThis : new InstanceObjectType(registry, this, nativeType); } else { this.kind = Kind.ORDINARY; this.typeOfThis = typeOfThis != null ? typeOfThis : registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE); } this.call = arrowType; } /** Creates an instance for a function that is an interface. */ private FunctionType(JSTypeRegistry registry, String name, Node source, TemplateTypeMap typeParameters) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE), false, typeParameters); setPrettyPrint(true); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkArgument(name != null); this.source = source;

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null); this.kind = Kind.INTERFACE; this.typeOfThis = new InstanceObjectType(registry, this); } /** Creates an instance for a function that is an interface. */ static FunctionType forInterface( JSTypeRegistry registry, String name, Node source, TemplateTypeMap typeParameters) { return new FunctionType(registry, name, source, typeParameters); } @Override public boolean isInstanceType() { // The universal constructor is its own instance, bizarrely. It overrides // getConstructor() appropriately when it's declared. return this == registry.getNativeType(U2U_CONSTRUCTOR_TYPE); } @Override public boolean isConstructor() { return kind == Kind.CONSTRUCTOR; } @Override public boolean isInterface() { return kind == Kind.INTERFACE; } @Override public boolean isOrdinaryFunction() { return kind == Kind.ORDINARY; } /** * When a class B inherits from A and A is annotated as a struct, then B * automatically gets the annotation, even if B's constructor is not * explicitly annotated. */ public boolean makesStructs() { if (!isConstructor()) { return false; } if (propAccess == PropAccess.STRUCT) { return true; } FunctionType superc = getSuperClassConstructor(); if (superc != null && superc.makesStructs()) { setStruct(); return true; } return false; } /** * When a class B inherits from A and A is annotated as a dict, then B * automatically gets the annotation, even if B's constructor is not * explicitly annotated. */ public boolean makesDicts() { if (!isConstructor()) { return false; } if (propAccess == PropAccess.DICT) { return true; } FunctionType superc = getSuperClassConstructor(); if (superc != null && superc.makesDicts()) { setDict(); return true; } return false; } public void setStruct() { propAccess = PropAccess.STRUCT; } public void setDict() { propAccess = PropAccess.DICT; } @Override public FunctionType toMaybeFunctionType() { return this; } @Override public boolean canBeCalled() { return true; } public boolean hasImplementedInterfaces() { if (!implementedInterfaces.isEmpty()){ return true; } FunctionType superCtor = isConstructor() ? getSuperClassConstructor() : null; if (superCtor != null) { return superCtor.hasImplementedInterfaces(); } return false; } public Iterable<Node> getParameters() { Node n = getParametersNode(); if (n != null) { return n.children(); } else { return Collections.emptySet(); } } /** Gets an LP node that contains all params. May be null. */ public Node getParametersNode() { return call.parameters; } /** Gets the minimum number of arguments that this function requires. */ public int getMinArguments() { // NOTE(

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>nicksantos): There are some native functions that have optional // parameters before required parameters. This algorithm finds the position // of the last required parameter. int i = 0; int min = 0; for (Node n : getParameters()) { i++; if (!n.isOptionalArg() && !n.isVarArgs()) { min = i; } } return min; } /** * Gets the maximum number of arguments that this function requires, * or Integer.MAX_VALUE if this is a variable argument function. */ public int getMaxArguments() { Node params = getParametersNode(); if (params != null) { Node lastParam = params.getLastChild(); if (lastParam == null || !lastParam.isVarArgs()) { return params.getChildCount(); } } return Integer.MAX_VALUE; } public JSType getReturnType() { return call.returnType; } public boolean isReturnTypeInferred() { return call.returnTypeInferred; } /** Gets the internal arrow type. For use by subclasses only. */ ArrowType getInternalArrowType() { return call; } @Override public Property getSlot(String name) { if ("prototype".equals(name)) { // Lazy initialization of the prototype field. getPrototype(); return prototypeSlot; } else { return super.getSlot(name); } } /** * Includes the prototype iff someone has created it. We do not want * to expose the prototype for ordinary functions. */ @Override public Set<String> getOwnPropertyNames() { if (prototypeSlot == null) { return super.getOwnPropertyNames(); } else { Set<String> names = Sets.newHashSet("prototype"); names.addAll(super.getOwnPropertyNames()); return names; } } /** * Gets the {@code prototype} property of this function type. This is * equivalent to {@code (ObjectType) getPropertyType("prototype")}. */ public ObjectType getPrototype() { // lazy initialization of the prototype field if (prototypeSlot == null) { String refName = getReferenceName(); if (refName == null) { // Someone is trying to access the prototype of a structural function. // We don't want to give real properties to this prototype, because // then it would propagate to all structural functions. setPrototypeNoCheck( registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE), null); } else { setPrototype( new PrototypeObjectType( registry, getReferenceName() + ".prototype", registry.getNativeObjectType(OBJECT_TYPE), isNativeObjectType(), null), null); } } return (ObjectType) prototypeSlot.getType(); } /** * Sets the prototype, creating the prototype object from the given * base type. * @param baseType The base type. */ public void setPrototypeBasedOn(ObjectType baseType) { setPrototypeBasedOn(baseType, null); } void setPrototypeBasedOn(ObjectType baseType, Node propertyNode) { // This is a bit weird. We need to successfully handle these // two cases: // Foo.prototype = new Bar();

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> // and // Foo.prototype = {baz: 3}; // In the first case, we do not want new properties to get // added to Bar. In the second case, we do want new properties // to get added to the type of the anonymous object. // // We handle this by breaking it into two cases: // // In the first case, we create a new PrototypeObjectType and set // its implicit prototype to the type being assigned. This ensures // that Bar will not get any properties of Foo.prototype, but properties // later assigned to Bar will get inherited properly. // // In the second case, we just use the anonymous object as the prototype. if (baseType.hasReferenceName() || isNativeObjectType() || baseType.isFunctionPrototypeType()) { baseType = new PrototypeObjectType( registry, getReferenceName() + ".prototype", baseType); } setPrototype(baseType, propertyNode); } /** * Extends the TemplateTypeMap of the function's this type, based on the * specified type. * @param type */ public void extendTemplateTypeMapBasedOn(ObjectType type) { typeOfThis.extendTemplateTypeMap(type.getTemplateTypeMap()); } /** * Sets the prototype. * @param prototype the prototype. If this value is {@code null} it will * silently be discarded. */ boolean setPrototype(ObjectType prototype, Node propertyNode) { if (prototype == null) { return false; } // getInstanceType fails if the function is not a constructor if (isConstructor() && prototype == getInstanceType()) { return false; } return setPrototypeNoCheck(prototype, propertyNode); } /** Set the prototype without doing any sanity checks. */ private boolean setPrototypeNoCheck(ObjectType prototype, Node propertyNode) { ObjectType oldPrototype = prototypeSlot == null ? null : (ObjectType) prototypeSlot.getType(); boolean replacedPrototype = oldPrototype != null; this.prototypeSlot = new Property("prototype", prototype, true, propertyNode == null ? source : propertyNode); prototype.setOwnerFunction(this); if (oldPrototype != null) { // Disassociating the old prototype makes this easier to debug-- // we don't have to worry about two prototypes running around. oldPrototype.setOwnerFunction(null); } if (isConstructor() || isInterface()) { FunctionType superClass = getSuperClassConstructor(); if (superClass != null) { superClass.addSubType(this); } if (isInterface()) { for (ObjectType interfaceType : getExtendedInterfaces()) { if (interfaceType.getConstructor() != null) { interfaceType.getConstructor().addSubType(this); } } } } if (replacedPrototype) { clearCachedValues(); } return true; } /** * Returns all interfaces implemented by a class or its superclass and any * superclasses for any of those interfaces. If this is called before all * types are resolved, it may return an incomplete set. */ public Iterable<ObjectType> getAllImplementedInterfaces() { // Store them in a linked hash set, so that the compile job is // deterministic. Set<ObjectType> interfaces = Sets.

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>newLinkedHashSet(); for (ObjectType type : getImplementedInterfaces()) { addRelatedInterfaces(type, interfaces); } return interfaces; } private void addRelatedInterfaces(ObjectType instance, Set<ObjectType> set) { FunctionType constructor = instance.getConstructor(); if (constructor != null) { if (!constructor.isInterface()) { return; } set.add(instance); for (ObjectType interfaceType : instance.getCtorExtendedInterfaces()) { addRelatedInterfaces(interfaceType, set); } } } /** Returns interfaces implemented directly by a class or its superclass. */ public Iterable<ObjectType> getImplementedInterfaces() { FunctionType superCtor = isConstructor() ? getSuperClassConstructor() : null; if (superCtor == null) { return implementedInterfaces; } else { return Iterables.concat( implementedInterfaces, superCtor.getImplementedInterfaces()); } } /** Returns interfaces directly implemented by the class. */ public Iterable<ObjectType> getOwnImplementedInterfaces() { return implementedInterfaces; } public void setImplementedInterfaces(List<ObjectType> implementedInterfaces) { if (isConstructor()) { // Records this type for each implemented interface. for (ObjectType type : implementedInterfaces) { registry.registerTypeImplementingInterface(this, type); typeOfThis.extendTemplateTypeMap(type.getTemplateTypeMap()); } this.implementedInterfaces = ImmutableList.copyOf(implementedInterfaces); } else { throw new UnsupportedOperationException(); } } /** * Returns all extended interfaces declared by an interfaces or its super- * interfaces. If this is called before all types are resolved, it may return * an incomplete set. */ public Iterable<ObjectType> getAllExtendedInterfaces() { // Store them in a linked hash set, so that the compile job is // deterministic. Set<ObjectType> extendedInterfaces = Sets.newLinkedHashSet(); for (ObjectType interfaceType : getExtendedInterfaces()) { addRelatedExtendedInterfaces(interfaceType, extendedInterfaces); } return extendedInterfaces; } private void addRelatedExtendedInterfaces(ObjectType instance, Set<ObjectType> set) { FunctionType constructor = instance.getConstructor(); if (constructor != null) { set.add(instance); for (ObjectType interfaceType : constructor.getExtendedInterfaces()) { addRelatedExtendedInterfaces(interfaceType, set); } } } /** Returns interfaces directly extended by an interface */ public Iterable<ObjectType> getExtendedInterfaces() { return extendedInterfaces; } /** Returns the number of interfaces directly extended by an interface */ public int getExtendedInterfacesCount() { return extendedInterfaces.size(); } public void setExtendedInterfaces(List<ObjectType> extendedInterfaces) throws UnsupportedOperationException { if (isInterface()) { this.extendedInterfaces = ImmutableList.copyOf(extendedInterfaces); for (ObjectType extendedInterface : this.extendedInterfaces) { typeOfThis.extendTemplateTypeMap( extendedInterface.getTemplateTypeMap()); } } else { throw new UnsupportedOperationException(); } } @Override public JSType getPropertyType(String name) { if (!hasOwnProperty(name)) { // Define the "call", "apply", and "bind" functions lazily. boolean isCall = "call".equals(name); boolean isBind =

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> // are optional. It's sufficient to check the first argument. Node firstArg = thisTypeNode.getNext(); if (firstArg == null || firstArg.isOptionalArg() || firstArg.isVarArgs()) { thisTypeNode.setOptionalArg(true); } } builder.withParamsNode(params); } return builder.build(); } @Override boolean defineProperty(String name, JSType type, boolean inferred, Node propertyNode) { if ("prototype".equals(name)) { ObjectType objType = type.toObjectType(); if (objType != null) { if (prototypeSlot != null && objType.isEquivalentTo(prototypeSlot.getType())) { return true; } setPrototypeBasedOn(objType, propertyNode); return true; } else { return false; } } return super.defineProperty(name, type, inferred, propertyNode); } /** * Computes the supremum or infimum of two functions. * Because sup() and inf() share a lot of logic for functions, we use * a single helper. * @param leastSuper If true, compute the supremum of {@code this} with * {@code that}. Otherwise, compute the infimum. * @return The least supertype or greatest subtype. */ FunctionType supAndInfHelper(FunctionType that, boolean leastSuper) { // NOTE(nicksantos): When we remove the unknown type, the function types // form a lattice with the universal constructor at the top of the lattice, // and the LEAST_FUNCTION_TYPE type at the bottom of the lattice. // // When we introduce the unknown type, it's much more difficult to make // heads or tails of the partial ordering of types, because there's no // clear hierarchy between the different components (parameter types and // return types) in the ArrowType. // // Rather than make the situation more complicated by introducing new // types (like unions of functions), we just fallback on the simpler // approach of getting things right at the top and the bottom of the // lattice. // // If there are unknown parameters or return types making things // ambiguous, then sup(A, B) is always the top function type, and // inf(A, B) is always the bottom function type. Preconditions.checkNotNull(that); if (isEquivalentTo(that)) { return this; } // If these are ordinary functions, then merge them. // Don't do this if any of the params/return // values are unknown, because then there will be cycles in // their local lattice and they will merge in weird ways. if (isOrdinaryFunction() && that.isOrdinaryFunction() && !this.call.hasUnknownParamsOrReturn() && !that.call.hasUnknownParamsOrReturn()) { // Check for the degenerate case, but double check // that there's not a cycle. boolean isSubtypeOfThat = isSubtype(that); boolean isSubtypeOfThis = that.isSubtype(this); if (isSubtypeOfThat && !isSubtypeOfThis) { return leastSuper ? that : this; } else if (isSubtypeOfThis && !

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>isSubtypeOfThat) { return leastSuper ? this : that; } // Merge the two functions component-wise. FunctionType merged = tryMergeFunctionPiecewise(that, leastSuper); if (merged != null) { return merged; } } // The function instance type is a special case // that lives above the rest of the lattice. JSType functionInstance = registry.getNativeType( JSTypeNative.FUNCTION_INSTANCE_TYPE); if (functionInstance.isEquivalentTo(that)) { return leastSuper ? that : this; } else if (functionInstance.isEquivalentTo(this)) { return leastSuper ? this : that; } // In theory, we should be using the GREATEST_FUNCTION_TYPE as the // greatest function. In practice, we don't because it's way too // broad. The greatest function takes var_args None parameters, which // means that all parameters register a type warning. // // Instead, we use the U2U ctor type, which has unknown type args. FunctionType greatestFn = registry.getNativeFunctionType(JSTypeNative.U2U_CONSTRUCTOR_TYPE); FunctionType leastFn = registry.getNativeFunctionType(JSTypeNative.LEAST_FUNCTION_TYPE); return leastSuper ? greatestFn : leastFn; } /** * Try to get the sup/inf of two functions by looking at the * piecewise components. */ private FunctionType tryMergeFunctionPiecewise( FunctionType other, boolean leastSuper) { Node newParamsNode = null; if (call.hasEqualParameters(other.call, EquivalenceMethod.IDENTITY)) { newParamsNode = call.parameters; } else { // If the parameters are not equal, don't try to merge them. // Someday, we should try to merge the individual params. return null; } JSType newReturnType = leastSuper ? call.returnType.getLeastSupertype(other.call.returnType) : call.returnType.getGreatestSubtype(other.call.returnType); JSType newTypeOfThis = null; if (isEquivalent(typeOfThis, other.typeOfThis)) { newTypeOfThis = typeOfThis; } else { JSType maybeNewTypeOfThis = leastSuper ? typeOfThis.getLeastSupertype(other.typeOfThis) : typeOfThis.getGreatestSubtype(other.typeOfThis); newTypeOfThis = maybeNewTypeOfThis; } boolean newReturnTypeInferred = call.returnTypeInferred || other.call.returnTypeInferred; return new FunctionType( registry, null, null, new ArrowType( registry, newParamsNode, newReturnType, newReturnTypeInferred), newTypeOfThis, null, false, false); } /** * Given a constructor or an interface type, get its superclass constructor * or {@code null} if none exists. */ public FunctionType getSuperClassConstructor() { Preconditions.checkArgument(isConstructor() || isInterface()); ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return null; } return maybeSuperInstanceType.getConstructor(); } /** * Given an

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> interface and a property, finds the top-most super interface * that has the property defined (including this interface). */ public static ObjectType getTopDefiningInterface(ObjectType type, String propertyName) { ObjectType foundType = null; if (type.hasProperty(propertyName)) { foundType = type; } for (ObjectType interfaceType : type.getCtorExtendedInterfaces()) { if (interfaceType.hasProperty(propertyName)) { foundType = getTopDefiningInterface(interfaceType, propertyName); } } return foundType; } /** * Given a constructor or an interface type and a property, finds the * top-most superclass that has the property defined (including this * constructor). */ public ObjectType getTopMostDefiningType(String propertyName) { Preconditions.checkState(isConstructor() || isInterface()); Preconditions.checkArgument(getInstanceType().hasProperty(propertyName)); FunctionType ctor = this; if (isInterface()) { return getTopDefiningInterface(getInstanceType(), propertyName); } ObjectType topInstanceType = null; do { topInstanceType = ctor.getInstanceType(); ctor = ctor.getSuperClassConstructor(); } while (ctor != null && ctor.getPrototype().hasProperty(propertyName)); return topInstanceType; } /** * Two function types are equal if their signatures match. Since they don't * have signatures, two interfaces are equal if their names match. */ boolean checkFunctionEquivalenceHelper( FunctionType that, EquivalenceMethod eqMethod) { if (isConstructor()) { if (that.isConstructor()) { return this == that; } return false; } if (isInterface()) { if (that.isInterface()) { return getReferenceName().equals(that.getReferenceName()); } return false; } if (that.isInterface()) { return false; } return typeOfThis.checkEquivalenceHelper(that.typeOfThis, eqMethod) && call.checkArrowEquivalenceHelper(that.call, eqMethod); } @Override public int hashCode() { return isInterface() ? getReferenceName().hashCode() : call.hashCode(); } public boolean hasEqualCallType(FunctionType otherType) { return this.call.checkArrowEquivalenceHelper( otherType.call, EquivalenceMethod.IDENTITY); } /** * Informally, a function is represented by * {@code function (params): returnType} where the {@code params} is a comma * separated list of types, the first one being a special * {@code this:T} if the function expects a known type for {@code this}. */ @Override String toStringHelper(boolean forAnnotations) { if (!isPrettyPrint() || this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) { return "Function"; } setPrettyPrint(false); StringBuilder b = new StringBuilder(32); b.append("function ("); int paramNum = call.parameters.getChildCount(); boolean hasKnownTypeOfThis = !(typeOfThis instanceof UnknownType); if (hasKnownTypeOfThis) { if (isConstructor()) { b.append("new:"); } else {

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS> b.append("this:"); } b.append(typeOfThis.toStringHelper(forAnnotations)); } if (paramNum > 0) { if (hasKnownTypeOfThis) { b.append(", "); } Node p = call.parameters.getFirstChild(); appendArgString(b, p, forAnnotations); p = p.getNext(); while (p != null) { b.append(", "); appendArgString(b, p, forAnnotations); p = p.getNext(); } } b.append("): "); b.append(call.returnType.toStringHelper(forAnnotations)); setPrettyPrint(true); return b.toString(); } private void appendArgString( StringBuilder b, Node p, boolean forAnnotations) { if (p.isVarArgs()) { appendVarArgsString(b, p.getJSType(), forAnnotations); } else if (p.isOptionalArg()) { appendOptionalArgString(b, p.getJSType(), forAnnotations); } else { b.append(p.getJSType().toStringHelper(forAnnotations)); } } /** Gets the string representation of a var args param. */ private void appendVarArgsString(StringBuilder builder, JSType paramType, boolean forAnnotations) { if (paramType.isUnionType()) { // Remove the optionality from the var arg. paramType = paramType.toMaybeUnionType().getRestrictedUnion( registry.getNativeType(JSTypeNative.VOID_TYPE)); } builder.append("...[").append( paramType.toStringHelper(forAnnotations)).append("]"); } /** Gets the string representation of an optional param. */ private void appendOptionalArgString( StringBuilder builder, JSType paramType, boolean forAnnotations) { if (paramType.isUnionType()) { // Remove the optionality from the var arg. paramType = paramType.toMaybeUnionType().getRestrictedUnion( registry.getNativeType(JSTypeNative.VOID_TYPE)); } builder.append(paramType.toStringHelper(forAnnotations)).append("="); } /** * A function is a subtype of another if their call methods are related via * subtyping and {@code this} is a subtype of {@code that} with regard to * the prototype chain. */ @Override public boolean isSubtype(JSType that) { if (JSType.isSubtypeHelper(this, that)) { return true; } if (that.isFunctionType()) { FunctionType other = that.toMaybeFunctionType(); if (other.isInterface()) { // Any function can be assigned to an interface function. return true; } if (isInterface()) { // An interface function cannot be assigned to anything. return false; } // If functionA is a subtype of functionB, then their "this" types // should be contravariant. However, this causes problems because // of the way we enforce overrides. Because function(this:SubFoo) // is not a subtype of function(this:Foo), our override check treats // this as an error. Let's punt on all this for now. // TODO(nicksan

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>tos): fix this. boolean treatThisTypesAsCovariant = // An interface 'this'-type is non-restrictive. // In practical terms, if C implements I, and I has a method m, // then any m doesn't necessarily have to C#m's 'this' // type doesn't need to match I. (other.typeOfThis.toObjectType() != null && other.typeOfThis.toObjectType().getConstructor() != null && other.typeOfThis.toObjectType().getConstructor().isInterface()) || // If one of the 'this' types is covariant of the other, // then we'll treat them as covariant (see comment above). other.typeOfThis.isSubtype(this.typeOfThis) || this.typeOfThis.isSubtype(other.typeOfThis); return treatThisTypesAsCovariant && this.call.isSubtype(other.call); } return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE).isSubtype(that); } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseFunctionType(this); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseFunctionType(this, that); } /** * Gets the type of instance of this function. * @throws IllegalStateException if this function is not a constructor * (see {@link #isConstructor()}). */ public ObjectType getInstanceType() { Preconditions.checkState(hasInstanceType()); return typeOfThis.toObjectType(); } /** * Sets the instance type. This should only be used for special * native types. */ void setInstanceType(ObjectType instanceType) { typeOfThis = instanceType; } /** * Returns whether this function type has an instance type. */ public boolean hasInstanceType() { return isConstructor() || isInterface(); } /** * Gets the type of {@code this} in this function. */ @Override public JSType getTypeOfThis() { return typeOfThis.isEmptyType() ? registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE) : typeOfThis; } /** * Gets the source node or null if this is an unknown function. */ public Node getSource() { return source; } /** * Sets the source node. */ public void setSource(Node source) { if (prototypeSlot != null) { // NOTE(bashir): On one hand when source is null we want to drop any // references to old nodes retained in prototypeSlot. On the other hand // we cannot simply drop prototypeSlot, so we retain all information // except the propertyNode for which we use an approximation! These // details mostly matter in hot-swap passes. if (source == null || prototypeSlot.getNode() == null) { prototypeSlot = new Property(prototypeSlot.getName(), prototypeSlot.getType(), prototypeSlot.isTypeInferred(), source); } } this.source = source; } /** Adds a type to the list of subtypes for this type. */ private void addSubType(FunctionType subType) { if (subTypes == null

Closure, 172

<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB> * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { <CHANGES> <CHANGEE> return false; <CHANGES> <CHANGEE> } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; <FILEE> <SCANS>) { subTypes = Lists.newArrayList(); } subTypes.add(subType); } @Override public void clearCachedValues() { super.clearCachedValues(); if (subTypes != null) { for (FunctionType subType : subTypes) { subType.clearCachedValues(); } } if (!isNativeObjectType()) { if (hasInstanceType()) { getInstanceType().clearCachedValues(); } if (prototypeSlot != null) { ((ObjectType) prototypeSlot.getType()).clearCachedValues(); } } } /** * Returns a list of types that are subtypes of this type. This is only valid * for constructor functions, and may be null. This allows a downward * traversal of the subtype graph. */ public List<FunctionType> getSubTypes() { return subTypes; } @Override public boolean hasCachedValues() { return prototypeSlot != null || super.hasCachedValues(); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); call = (ArrowType) safeResolve(call, t, scope); if (prototypeSlot != null) { prototypeSlot.setType( safeResolve(prototypeSlot.getType(), t, scope)); } // Warning about typeOfThis if it doesn't resolve to an ObjectType // is handled further upstream. // // TODO(nicksantos): Handle this correctly if we have a UnionType. // // TODO(nicksantos): In ES3, the run-time coerces "null" to the global // activation object. In ES5, it leaves it as null. Just punt on this // issue for now by coercing out null. This is complicated by the // fact that when most people write @this {Foo}, they really don't // mean "nullable Foo". For certain tags (like @extends) we de-nullify // the name for them. JSType maybeTypeOfThis = safeResolve(typeOfThis, t, scope); if (maybeTypeOfThis != null) { maybeTypeOfThis = maybeTypeOfThis.restrictByNotNullOrUndefined(); } if (maybeTypeOfThis instanceof ObjectType) { typeOfThis = maybeTypeOfThis; } boolean changed = false; ImmutableList.Builder<ObjectType> resolvedInterfaces = ImmutableList.builder(); for (ObjectType iface : implementedInterfaces) { ObjectType resolvedIface = (ObjectType) iface.resolve(t, scope); resolvedInterfaces.add(resolvedIface); changed |= (resolvedIface != iface); } if (changed) { implementedInterfaces = resolvedInterfaces.build(); } if (subTypes != null) { for (int i = 0; i < subTypes.size(); i++) { subTypes.set( i, JSType.toMaybeFunctionType(subTypes.get(i).resolve(t, scope))); } } return super.resolveInternal(t, scope); } @Override public String toDebugHashCodeString() { if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) { return super.toDebugHashCodeString(); } StringBuilder b = new StringBuilder(